ThsStaking

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/ths-staking/ThsStaking.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {IThsStaking, TierStake} from "./interfaces/IThsStaking.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC1363Receiver} from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol";
import {
    ThsStakingStorage,
    ThsStakingStorageLib,
    Tier,
    Epoch,
    StakeUpdateList,
    StakeUpdate,
    Stake,
    User,
    UserTier
} from "./lib/ThsStakingStorage.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ITiersManager} from "./tiers-managers/interfaces/ITiersManager.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ACLConsumer} from "../aux/ACLConsumer.sol";

contract ThsStaking is
    IThsStaking,
    UUPSUpgradeable,
    ReentrancyGuardUpgradeable,
    PausableUpgradeable,
    IERC1363Receiver,
    ACLConsumer
{
    using ThsStakingStorageLib for ThsStakingStorage;
    using SafeERC20 for IERC20;

    uint256 public constant REWARD_SCALE = 1e24;
    uint256 public constant BPS_SCALE = 10000;
    uint48 public constant EPOCH_DURATION = 1 days;
    uint48 public constant STAKE_DURATION_EPOCHS = 30;

    IERC20 public immutable thsToken;
    IERC20 public immutable wbtc;
    address public immutable treasury;

    // MODIFIERS

    modifier onlyThsSale() {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        require(msg.sender == s.thsSale, OnlyThsSale());
        _;
    }

    // CONSTRUCTOR & INITIALIZER

    constructor(address aclManagerAddress, address thsTokenAddress, address wbtcAddress, address treasuryAddress)
        ACLConsumer(aclManagerAddress)
    {
        require(thsTokenAddress != address(0), ZeroAddress());
        require(wbtcAddress != address(0), ZeroAddress());
        require(treasuryAddress != address(0), ZeroAddress());
        _disableInitializers();
        thsToken = IERC20(thsTokenAddress);
        wbtc = IERC20(wbtcAddress);
        treasury = treasuryAddress;
    }

    function initialize(address tiersManagerAddress) external initializer {
        require(tiersManagerAddress != address(0), ZeroAddress());
        __UUPSUpgradeable_init();
        __ReentrancyGuard_init();
        __Pausable_init();

        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        s.tiersManager = tiersManagerAddress;
    }

    // MUTATIVE FUNCTIONS

    function setThsSale(address newThsSaleAddress) external onlyProtocolAdmin {
        require(newThsSaleAddress != address(0), InvalidThsSaleAddress());
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        s.thsSale = newThsSaleAddress;
        emit ThsSaleUpdated(newThsSaleAddress);
    }

    function setTiersManager(address newTiersManagerAddress) external onlyProtocolAdmin {
        require(newTiersManagerAddress != address(0), InvalidTiersManagerAddress());
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        s.tiersManager = newTiersManagerAddress;
        emit TiersManagerUpdated(newTiersManagerAddress);
    }

    function pause() external onlyProtocolAdmin {
        _pause();
    }

    function unpause() external onlyProtocolAdmin {
        _unpause();
    }

    function depositDailyRewards(uint256 totalWbtc) external onlyRewardsDistributor nonReentrant {
        require(totalWbtc != 0, InvalidWbtcAmount());

        wbtc.safeTransferFrom(msg.sender, address(this), totalWbtc);
        _distributeRewards(totalWbtc);
    }

    function stake(uint128 amount) external nonReentrant whenNotPaused {
        thsToken.safeTransferFrom(msg.sender, address(this), amount);
        _stake(amount, msg.sender, uint48(block.timestamp));
    }

    function stakeAt(address user, uint128 amount, uint48 startAt) external onlyThsSale whenNotPaused {
        require(user != address(0), ZeroAddress());
        require(amount > 0, ZeroAmount());
        _stake(amount, user, startAt);
    }

    function unstake(uint8[] calldata tierIds, uint64[][] calldata stakeIds) external nonReentrant whenNotPaused {
        _unstake(tierIds, stakeIds);
    }

    function claimRewards(uint8[] calldata tierIds) external nonReentrant whenNotPaused {
        _claimRewards(tierIds);
    }

    function restake(uint8 tierId, uint64 stakeId) external whenNotPaused {
        _restake(tierId, stakeId);
    }

    // IERC1363Receiver implementation

    function onTransferReceived(
        address, // operator - not used in this implementation
        address from,
        uint256 value,
        bytes calldata // data - not used in this implementation
    )
        external
        nonReentrant
        whenNotPaused
        returns (bytes4)
    {
        require(msg.sender == address(thsToken), InvalidToken());
        require(value <= type(uint128).max, InvalidStakeAmount());

        _stake(uint128(value), from, uint48(block.timestamp));

        return IERC1363Receiver.onTransferReceived.selector;
    }

    // INTERNAL FUNCTIONS

    function _processUserTier(address user, uint8 tierId) internal {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        User storage userStorage = s.users[user];
        UserTier storage userTier = userStorage.userTiers[tierId];
        Tier storage tier = s.tiers[tierId];
        uint48 processUntil = s.lastRewardEpoch;
        if (processUntil == 0) {
            s.lastRewardEpoch = getCurrentEpoch();
            return;
        }

        uint48 lastProcessed = userTier.lastProcessedEpoch;

        if (lastProcessed > processUntil) {
            return;
        }

        uint128 balance = userTier.totalStakedThs;
        uint128 accruedRewards = 0;

        uint48 incomingEpoch = userTier.incomingThs.startEpoch;
        uint48 outgoingEpoch = userTier.outgoingThs.startEpoch;
        uint48 lastUserProcessedEpoch = lastProcessed;

        while (lastUserProcessedEpoch < processUntil) {
            uint48 nextEventEpoch = processUntil;

            if (incomingEpoch != 0 && incomingEpoch < nextEventEpoch) {
                nextEventEpoch = incomingEpoch;
            }
            if (outgoingEpoch != 0 && outgoingEpoch < nextEventEpoch) {
                nextEventEpoch = outgoingEpoch;
            }

            uint128 oldBalance = balance;
            uint256 prevScaledReward = tier.epochs[lastUserProcessedEpoch].scaledRewardPerThs;

            // Check if we're processing multiple epochs at once (gap between processed and event epochs)
            if (nextEventEpoch > lastUserProcessedEpoch + 1) {
                // Calculate rewards for the entire skipped period using the reward rate at the end of the period
                uint256 endPeriodReward = tier.epochs[nextEventEpoch - 1].scaledRewardPerThs;
                if (endPeriodReward > prevScaledReward && oldBalance > 0) {
                    // Accrue rewards for the period from lastUserProcessedEpoch to nextEventEpoch-1
                    accruedRewards += SafeCast.toUint128(
                        Math.mulDiv(uint256(oldBalance), endPeriodReward - prevScaledReward, REWARD_SCALE)
                    );
                }
                // Update prevScaledReward for the final epoch segment calculation
                prevScaledReward = endPeriodReward;
            }

            if (nextEventEpoch == incomingEpoch) {
                balance += userTier.incomingThs.updates[nextEventEpoch].thsAmount;
                incomingEpoch = userTier.incomingThs.updates[nextEventEpoch].nextUpdateEpoch;
                delete userTier.incomingThs.updates[nextEventEpoch];

                if (incomingEpoch == 0) {
                    userTier.incomingThs.endEpoch = 0;
                }
            }
            if (nextEventEpoch == outgoingEpoch) {
                balance -= userTier.outgoingThs.updates[nextEventEpoch].thsAmount;
                outgoingEpoch = userTier.outgoingThs.updates[nextEventEpoch].nextUpdateEpoch;
                delete userTier.outgoingThs.updates[nextEventEpoch];

                if (outgoingEpoch == 0) {
                    userTier.outgoingThs.endEpoch = 0;
                }
            }

            uint256 currentScaledReward = tier.epochs[nextEventEpoch].scaledRewardPerThs;

            // Accrue rewards for the final epoch segment (from nextEventEpoch-1 to nextEventEpoch)
            if (currentScaledReward > prevScaledReward && balance > 0) {
                accruedRewards += SafeCast.toUint128(
                    Math.mulDiv(uint256(balance), currentScaledReward - prevScaledReward, REWARD_SCALE)
                );
            }

            lastUserProcessedEpoch = nextEventEpoch;
        }

        userTier.totalStakedThs = balance;
        userTier.lastProcessedEpoch = processUntil;
        if (accruedRewards > 0) {
            userStorage.unclaimedRewards += accruedRewards;
        }

        userTier.incomingThs.startEpoch = incomingEpoch;
        if (incomingEpoch != 0) {
            userTier.incomingThs.updates[incomingEpoch].prevUpdateEpoch = 0;
        }
        userTier.outgoingThs.startEpoch = outgoingEpoch;
        if (outgoingEpoch != 0) {
            userTier.outgoingThs.updates[outgoingEpoch].prevUpdateEpoch = 0;
        }
    }

    function _stake(uint128 amount, address user, uint48 start) internal {
        require(amount > 0, InvalidStakeAmount());

        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        uint8 tierId = ITiersManager(s.tiersManager).getTierIdByAmount(amount);

        _processUserTier(user, tierId);

        uint48 entryEpoch = _timestampToEpoch(start) + 1;
        uint48 expiryEpoch = entryEpoch + STAKE_DURATION_EPOCHS;

        UserTier storage userTier = s.users[user].userTiers[tierId];

        s.tiers[tierId].epochs[entryEpoch].incomingThs += amount;
        s.tiers[tierId].epochs[expiryEpoch].outgoingThs += amount;

        _insertStakeUpdate(userTier.incomingThs, entryEpoch, amount);
        _insertStakeUpdate(userTier.outgoingThs, expiryEpoch, amount);

        uint64 stakeId = ++s.lastStakeId;
        userTier.stakes[stakeId] = Stake({start: start, expiry: expiryEpoch * EPOCH_DURATION, amount: uint128(amount)});

        emit Staked(user, amount, tierId, stakeId, entryEpoch, expiryEpoch);
    }

    function _unstake(uint8[] calldata tierIds, uint64[][] calldata stakeIds) internal {
        require(tierIds.length == stakeIds.length, InvalidInputLength());

        for (uint8 i = 0; i < tierIds.length; i++) {
            _processUnstakeInTier(msg.sender, tierIds[i], stakeIds[i]);
        }
    }

    function _processUnstakeInTier(address user, uint8 tierId, uint64[] calldata stakeIds) internal {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        UserTier storage userTier = s.users[user].userTiers[tierId];
        uint128 totalWithdrawn = 0;
        uint48 currentEpoch = getCurrentEpoch();

        for (uint256 i = 0; i < stakeIds.length; i++) {
            uint64 stakeId = stakeIds[i];
            Stake memory stakeData = userTier.stakes[stakeId];

            uint128 amount = stakeData.amount;

            require(amount != 0, StakeNotFound());
            require(block.timestamp >= stakeData.start, StakeNotStarted());
            uint48 entryEpoch = _timestampToEpoch(stakeData.start) + 1;
            uint48 expiryEpoch = _timestampToEpoch(stakeData.expiry);

            if (block.timestamp < stakeData.expiry) {
                if (entryEpoch > currentEpoch) {
                    // Stake hasn't started yet: just cancel future incoming
                    s.tiers[tierId].epochs[entryEpoch].incomingThs -= amount;
                    _removeStakeUpdate(userTier.incomingThs, entryEpoch, amount);
                    _processUserTier(user, tierId);
                } else {
                    // Stake is active: move the outgoing to current epoch to exclude from next distribution
                    s.tiers[tierId].epochs[currentEpoch].outgoingThs += amount;
                    _insertStakeUpdate(userTier.outgoingThs, currentEpoch, amount);
                }

                // Always remove the future expiry
                s.tiers[tierId].epochs[expiryEpoch].outgoingThs -= amount;
                _removeStakeUpdate(userTier.outgoingThs, expiryEpoch, amount);
            }

            totalWithdrawn += amount;
            delete userTier.stakes[stakeId];

            emit Unstaked(user, amount, tierId, stakeId);
        }

        _processUserTier(user, tierId);
        thsToken.safeTransfer(user, totalWithdrawn);
    }

    function _claimRewards(uint8[] calldata tierIds) internal {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        User storage userStorage = s.users[msg.sender];

        for (uint256 i = 0; i < tierIds.length; i++) {
            _processUserTier(msg.sender, tierIds[i]);
        }

        uint256 rewardsToClaim = userStorage.unclaimedRewards;
        require(rewardsToClaim > 0, NoRewardsToClaim());

        userStorage.unclaimedRewards = 0;
        wbtc.safeTransfer(msg.sender, rewardsToClaim);

        emit RewardsClaimed(msg.sender, rewardsToClaim);
    }

    function _restake(uint8 tierId, uint64 stakeId) internal {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        UserTier storage userTier = s.users[msg.sender].userTiers[tierId];
        Stake memory stakeData = userTier.stakes[stakeId];

        uint128 amount = stakeData.amount;
        require(amount != 0, StakeNotFound());

        uint48 expiryEpoch = _timestampToEpoch(stakeData.expiry);
        uint48 currentEpoch = getCurrentEpoch();

        require(currentEpoch >= expiryEpoch - 1, RestakeTooEarly());

        _processUserTier(msg.sender, tierId);

        uint64 oldStakeId = stakeId;
        uint8 oldTierId = tierId;

        if (currentEpoch < expiryEpoch) {
            // Prolongation scenario: restaking before expiry epoch
            // Remove the original outgoing update from expiryEpoch
            s.tiers[tierId].epochs[expiryEpoch].outgoingThs -= amount;
            _removeStakeUpdate(userTier.outgoingThs, expiryEpoch, amount);

            // Add new outgoing update at prolonged expiry
            uint48 newExpiryEpoch = expiryEpoch + STAKE_DURATION_EPOCHS;
            s.tiers[tierId].epochs[newExpiryEpoch].outgoingThs += amount;
            _insertStakeUpdate(userTier.outgoingThs, newExpiryEpoch, amount);

            // Update stake storage with new expiry
            userTier.stakes[stakeId].expiry = newExpiryEpoch * EPOCH_DURATION;

            emit Restaked(msg.sender, amount, oldTierId, oldStakeId, tierId, stakeId, expiryEpoch, newExpiryEpoch);
        } else {
            // New stake scenario: restaking at or after expiry epoch
            delete userTier.stakes[stakeId];

            uint48 newStart = currentEpoch * EPOCH_DURATION;

            uint8 newTierId = ITiersManager(s.tiersManager).getTierIdByAmount(amount);
            _stake(amount, msg.sender, newStart);

            uint64 newStakeId = s.lastStakeId;
            uint48 newEntryEpoch = _timestampToEpoch(newStart) + 1;
            uint48 newExpiryEpoch = newEntryEpoch + STAKE_DURATION_EPOCHS;

            emit Restaked(
                msg.sender, amount, oldTierId, oldStakeId, newTierId, newStakeId, newEntryEpoch, newExpiryEpoch
            );
        }
    }

    function _insertStakeUpdate(StakeUpdateList storage list, uint48 epoch, uint128 amount) internal {
        StakeUpdate storage update = list.updates[epoch];
        if (update.thsAmount > 0) {
            update.thsAmount += amount;
            return;
        }

        uint48 startEpoch = list.startEpoch;
        uint48 endEpoch = list.endEpoch;

        if (startEpoch == 0) {
            list.startEpoch = list.endEpoch = epoch;
            list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: 0, nextUpdateEpoch: 0});
        } else if (epoch < startEpoch) {
            list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: 0, nextUpdateEpoch: startEpoch});
            list.updates[startEpoch].prevUpdateEpoch = epoch;
            list.startEpoch = epoch;
        } else if (epoch > endEpoch) {
            list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: endEpoch, nextUpdateEpoch: 0});
            list.updates[endEpoch].nextUpdateEpoch = epoch;
            list.endEpoch = epoch;
        } else {
            uint48 current = startEpoch;
            uint48 next = list.updates[current].nextUpdateEpoch;
            while (next < epoch) {
                current = next;
                next = list.updates[current].nextUpdateEpoch;
            }

            list.updates[epoch] = StakeUpdate({thsAmount: amount, prevUpdateEpoch: current, nextUpdateEpoch: next});
            list.updates[current].nextUpdateEpoch = epoch;
            list.updates[next].prevUpdateEpoch = epoch;
        }
    }

    function _removeStakeUpdate(StakeUpdateList storage list, uint48 epoch, uint128 amount) internal {
        StakeUpdate storage update = list.updates[epoch];
        require(update.thsAmount >= amount, InvalidStakeAmount());

        update.thsAmount -= amount;

        if (update.thsAmount == 0) {
            uint48 prev = update.prevUpdateEpoch;
            uint48 next = update.nextUpdateEpoch;

            if (prev != 0) {
                list.updates[prev].nextUpdateEpoch = next;
            } else {
                list.startEpoch = next;
            }

            if (next != 0) {
                list.updates[next].prevUpdateEpoch = prev;
            } else {
                list.endEpoch = prev;
            }

            delete list.updates[epoch];
        }
    }

    function _distributeRewards(uint256 totalWbtc) internal {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        (uint8[] memory tierIds, uint256[] memory feeBps) = ITiersManager(s.tiersManager).getTiers();
        uint256 tierCount = tierIds.length;
        uint48 currentEpoch = getCurrentEpoch();
        uint48 lastRewardEpoch = s.lastRewardEpoch;

        uint128[] memory activeStakedInTiers = new uint128[](tierCount);
        uint256 totalStakedAcrossAllTiers;

        // Enforce processing strictly previous epochs while supporting backfilling
        uint48 epochToProcess;
        if (lastRewardEpoch == 0) {
            require(currentEpoch > 0, InvalidEpoch());
            epochToProcess = currentEpoch - 1;
        } else {
            require(lastRewardEpoch < currentEpoch - 1, InvalidEpoch());
            epochToProcess = lastRewardEpoch + 1;
        }

        for (uint256 i = 0; i < tierCount; i++) {
            uint8 tierId = tierIds[i];
            Tier storage tier = s.tiers[tierId];

            uint128 totalStakedThs = tier.totalStakedThs;

            // Apply flows for the epoch being processed
            totalStakedThs =
                totalStakedThs + tier.epochs[epochToProcess].incomingThs - tier.epochs[epochToProcess].outgoingThs;

            activeStakedInTiers[i] = totalStakedThs;
            totalStakedAcrossAllTiers += totalStakedThs;

            tier.totalStakedThs = totalStakedThs;
        }

        uint256 totalNetRewardsForAllStakers;

        // If no active stakers, send 100% to treasury
        if (totalStakedAcrossAllTiers == 0) {
            wbtc.safeTransfer(treasury, totalWbtc);
            emit RewardsDeposited(totalWbtc, totalWbtc, 0, uint8(tierCount));
            s.lastRewardEpoch = epochToProcess;
            return;
        }

        for (uint256 i = 0; i < tierCount; i++) {
            uint8 tierId = tierIds[i];
            Tier storage tier = s.tiers[tierId];
            uint128 stakedInTier = activeStakedInTiers[i];

            uint256 prevScaledRewardPerThs = tier.epochs[lastRewardEpoch].scaledRewardPerThs;

            if (stakedInTier == 0) {
                tier.epochs[epochToProcess].scaledRewardPerThs = prevScaledRewardPerThs;
                emit NoStakedThsInTier(epochToProcess, tierId, prevScaledRewardPerThs);
                continue;
            }

            uint256 grossRewardsForTier = Math.mulDiv(totalWbtc, stakedInTier, totalStakedAcrossAllTiers);
            uint256 netRewardsForTierStakers = Math.mulDiv(grossRewardsForTier, BPS_SCALE - feeBps[i], BPS_SCALE);
            totalNetRewardsForAllStakers += netRewardsForTierStakers;
            uint256 tierDeltaPerThs = Math.mulDiv(netRewardsForTierStakers, REWARD_SCALE, uint256(stakedInTier));
            uint256 newScaledTotal = prevScaledRewardPerThs + tierDeltaPerThs;

            Epoch storage epochData = tier.epochs[epochToProcess];
            epochData.scaledRewardPerThs = newScaledTotal;

            emit RewardsDepositedPerTier(epochToProcess, tierId, stakedInTier, grossRewardsForTier, tierDeltaPerThs);
        }

        s.lastRewardEpoch = epochToProcess;

        uint256 treasuryFee = totalWbtc - totalNetRewardsForAllStakers;

        if (treasuryFee > 0) {
            wbtc.safeTransfer(treasury, treasuryFee);
        }

        emit RewardsDeposited(totalWbtc, treasuryFee, totalStakedAcrossAllTiers, uint8(activeStakedInTiers.length));
    }

    function getCurrentEpoch() public view returns (uint48) {
        return _timestampToEpoch(uint48(block.timestamp));
    }

    function getTiersManager() external view returns (address) {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        return s.tiersManager;
    }

    function getLastRewardEpoch() external view returns (uint48) {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        return s.lastRewardEpoch;
    }

    function getThsSale() external view returns (address) {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        return s.thsSale;
    }

    function getTotalStakedAcrossAllTiers() external view returns (TierStake[] memory tierStakes, uint256 totalStaked) {
        ThsStakingStorage storage s = ThsStakingStorageLib.getStorage();
        (uint8[] memory tierIds,) = ITiersManager(s.tiersManager).getTiers();
        uint256 tierCount = tierIds.length;
        uint48 currentEpoch = getCurrentEpoch();
        uint48 lastRewardEpoch = s.lastRewardEpoch;

        tierStakes = new TierStake[](tierCount);
        totalStaked = 0;

        uint48 epochToProcess;
        if (lastRewardEpoch == 0) {
            require(currentEpoch > 0, InvalidEpoch());
            epochToProcess = currentEpoch - 1;
        } else {
            require(lastRewardEpoch < currentEpoch - 1, InvalidEpoch());
            epochToProcess = lastRewardEpoch + 1;
        }

        for (uint256 i = 0; i < tierCount; i++) {
            uint8 tierId = tierIds[i];
            Tier storage tier = s.tiers[tierId];

            uint128 totalStakedThs = tier.totalStakedThs;

            totalStakedThs =
                totalStakedThs + tier.epochs[epochToProcess].incomingThs - tier.epochs[epochToProcess].outgoingThs;

            tierStakes[i] = TierStake({tierId: tierId, totalStaked: totalStakedThs});

            totalStaked += totalStakedThs;
        }

        return (tierStakes, totalStaked);
    }

    function _timestampToEpoch(uint48 timestamp) internal pure returns (uint48) {
        return timestamp / EPOCH_DURATION;
    }

    // UUPS UPGRADE

    function _authorizeUpgrade(address newImplementation) internal override onlyProtocolAdmin {}
}
"
    },
    "src/ths-staking/interfaces/IThsStaking.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {IThsStakingEvents} from "./IThsStakingEvents.sol";
import {IThsStakingErrors} from "./IThsStakingErrors.sol";

struct TierStake {
    uint8 tierId;
    uint128 totalStaked;
}

interface IThsStaking is IThsStakingEvents, IThsStakingErrors {
    function setTiersManager(address newTiersManagerAddress) external;
    function depositDailyRewards(uint256 totalWbtc) external;
    function stake(uint128 amount) external;
    function stakeAt(address user, uint128 amount, uint48 startAt) external;
    function unstake(uint8[] calldata tierIds, uint64[][] calldata stakeIds) external;
    function claimRewards(uint8[] calldata tierIds) external;
    function restake(uint8 tierId, uint64 stakeId) external;
    function getTiersManager() external view returns (address);
    function getLastRewardEpoch() external view returns (uint48);
    function getThsSale() external view returns (address);
    function setThsSale(address newThsSaleAddress) external;
    function getTotalStakedAcrossAllTiers() external view returns (TierStake[] memory tierStakes, uint256 totalStaked);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.22;

import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
     *
     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
     * is expected to be the implementation slot in ERC-1967.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        } catch {
            // The implementation is not UUPS
            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if ($._status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        $._status = ENTERED;
    }

    function _nonReentrantAfter() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        return $._status == ENTERED;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Pausable
    struct PausableStorage {
        bool _paused;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;

    function _getPausableStorage() private pure returns (PausableStorage storage $) {
        assembly {
            $.slot := PausableStorageLocation
        }
    }

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    function __Pausable_init() internal onlyInitializing {
    }

    function __Pausable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        PausableStorage storage $ = _getPausableStorage();
        return $._paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
        emit Unpaused(_msgSender());
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @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);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363Receiver.sol)

pragma solidity >=0.5.0;

/**
 * @title IERC1363Receiver
 * @dev Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall`
 * from ERC-1363 token contracts.
 */
interface IERC1363Receiver {
    /**
     * @dev Whenever ERC-1363 tokens are transferred to this contract via `transferAndCall` or `transferFromAndCall`
     * by `operator` from `from`, this function is called.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))`
     * (i.e. 0x88a7ca5c, or its own function selector).
     *
     * @param operator The address which called `transferAndCall` or `transferFromAndCall` function.
     * @param from The address which the tokens are transferred from.
     * @param value The amount of tokens transferred.
     * @param data Additional data with no specified format.
     * @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` if transfer is allowed unless throwing.
     */
    function onTransferReceived(
        address operator,
        address from,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);
}
"
    },
    "src/ths-staking/lib/ThsStakingStorage.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

struct Epoch {
    uint128 incomingThs;
    uint128 outgoingThs;
    uint256 scaledRewardPerThs;
}

struct Tier {
    uint128 totalStakedThs;
    mapping(uint48 => Epoch) epochs;
}

struct StakeUpdate {
    uint128 thsAmount;
    uint48 prevUpdateEpoch;
    uint48 nextUpdateEpoch;
}

struct StakeUpdateList {
    uint48 startEpoch;
    uint48 endEpoch;
    mapping(uint48 => StakeUpdate) updates;
}

struct UserTier {
    uint128 totalStakedThs;
    uint48 lastProcessedEpoch;
    StakeUpdateList incomingThs;
    StakeUpdateList outgoingThs;
    mapping(uint64 => Stake) stakes;
}

struct User {
    uint128 unclaimedRewards;
    mapping(uint8 => UserTier) userTiers;
}

struct Stake {
    // Actual start, not epoch
    uint48 start;
    // Actual expiry, not epoch
    uint48 expiry;
    uint128 amount;
}

struct ThsStakingStorage {
    mapping(uint8 => Tier) tiers;
    mapping(address => User) users;
    uint64 lastStakeId;
    address tiersManager;
    uint48 lastRewardEpoch;
    address thsSale;
}

library ThsStakingStorageLib {
    bytes32 private constant THS_STAKING_STORAGE_POSITION = bytes32(uint256(keccak256("ths-staking.storage")) - 1);

    function getStorage() internal pure returns (ThsStakingStorage storage s) {
        bytes32 position = THS_STAKING_STORAGE_POSITION;
        assembly {
            s.slot := position
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Return the 512-bit addition of two uint256.
     *
     * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
     */
    function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        assembly ("memory-safe") {
            low := add(a, b)
            high := lt(low, a)
        }
    }

    /**
     * @dev Return the 512-bit multiplication of two uint256.
     *
     * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
     */
    function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
        // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
        // variables such that product = high * 2²⁵⁶ + low.
        assembly ("memory-safe") {
            let mm := mulmod(a, b, not(0))
            low := mul(a, b)
            high := sub(sub(mm, low), lt(mm, low))
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a + b;
            success = c >= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a - b;
            success = c <= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a * b;
            assembly ("memory-safe") {
                // Only true when the multiplication doesn't overflow
                // (c / a == b) || (a == 0)
                success := or(eq(div(c, a), b), iszero(a))
            }
            // equivalent to: success ? c : 0
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @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 {
            success = b > 0;
            assembly ("memory-safe") {
                // The `DIV` opcode returns zero when the denominator is 0.
                result := div(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 {
            success = b > 0;
            assembly ("memory-safe") {
                // The `MOD` opcode returns zero when the denominator is 0.
                result := mod(a, b)
            }
        }
    }

    /**
     * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryAdd(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
     */
    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
        (, uint256 result) = trySub(a, b);
        return result;
    }

    /**
     * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryMul(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * SafeCast.toUint(condition));
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(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.
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }

        // 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 SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
        }
    }

    /**
     * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     *
     * 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 {
            (uint256 high, uint256 low) = mul512(x, y);

            // Handle non-overflow cases, 256 by 256 division.
            if (high == 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 low / denominator;
            }

            // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
            if (denominator <= high) {
                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [high low].
            uint256 remainder;
            assembly ("memory-safe") {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                high := sub(high, gt(remainder, low))
                low := sub(low, 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 ("memory-safe") {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [high low] by twos.
                low := div(low, twos)

                // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from high into low.
            low |= high * twos;

            // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
            // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
            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⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
            inverse *= 2 - denominator * inverse; // inverse mod 2³²
            inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
            inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶

            // 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²⁵⁶. Since the preconditions guarantee that the outcome is
            // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
            // is no longer required.
            result = low * inverse;
            return result;
        }
    }

    /**
     * @dev 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) {
        return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    }

    /**
     * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
     */
    function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);
            if (high >= 1 << n) {
                Panic.panic(Panic.UNDER_OVERFLOW);
            }
            return (high << (256 - n)) | (low >> n);
        }
    }

    /**
     * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
     */
    function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
        return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
    }

    /**
     * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
     *
     * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
     * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
     *
     * If the input value is not inversible, 0 is returned.
     *
     * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
     * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
     */
    function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
        unchecked {
            if (n == 0) return 0;

            // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
            // Used to compute integers x and y such that: ax + ny = gcd(a, n).
            // When the gcd is 1, then the inverse of a modulo n exists and it's x.
            // ax + ny = 1
            // ax = 1 + (-y)n
            // ax ≡ 1 (mod n) # x is the inverse of a modulo n

            // If the remainder is 0 the gcd is n right away.
            uint256 remainder = a

Tags:
ERC20, ERC165, Multisig, Pausable, Upgradeable, Multi-Signature, Factory|addr:0x15b6ea64b061175a517923d0712f7fb7b48f0172|verified:true|block:23733418|tx:0x4df317d662f4f8cd5947ceb64a36049fa3daab6e04900db86563fa8a86d8861a|first_check:1762351147

Submitted on: 2025-11-05 14:59:09

Comments

Log in to comment.

No comments yet.