ETH Price: $2,916.38 (+3.25%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Amount:Between 1-100k
Reset Filter

Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Amount:Between 1-100k
Reset Filter

Advanced mode:
Parent Transaction Hash Method Block
From
To

There are no matching entries

Update your filters to view other transactions

View All Internal Transactions
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ZNDStaking

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 10000 runs

Other Settings:
paris EvmVersion
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {IZNDStaking} from "./IZNDStaking.sol";
import {ZNDTreasury} from "./ZNDTreasury.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
 * @notice Implements staking and withdrawal functionality for central platform and externally owned accounts.
 * @notice Implements rewards distribution functionality.
 * @notice EOA interaction require implementation of EIP-712 for signing messages.
 */
contract ZNDStaking is ZNDTreasury, IZNDStaking, EIP712 {
    using SafeERC20 for IERC20;

    using ECDSA for bytes32;

    using EnumerableSet for EnumerableSet.AddressSet;

    ///////////////////////////
    //   EIP712 Typehashes   //
    ///////////////////////////

    bytes32 private immutable _STAKE_EOA_TYPEHASH =
        keccak256("StakeEOA(uint256 plan,uint256 amount)");

    bytes32 private immutable _WITHDRAW_STAKE_EOA_TYPEHASH =
        keccak256("WithdrawStakeEOA(uint256 stakeId,uint256 amount)");

    bytes32 private immutable _WITHDRAW_REWARDS_EOA_TYPEHASH =
        keccak256("WithdrawRewardsEOA(uint256 amount)");

    ////////////////////////////
    //    State Constants     //
    ////////////////////////////

    /// @dev Number of tiers in the Tier enum
    uint256 private constant NUM_TIERS = 6;

    /// @dev Number of plans in the Plan enum
    uint256 private constant NUM_PLANS = 4;

    /// @dev Number of Pools in total
    uint256 private constant NUM_POOLS = NUM_PLANS * NUM_TIERS;

    /// @dev Flag value to represent false for uint256
    uint256 private constant FALSE = 1;

    /// @dev Flag value to represent true for uint256
    uint256 private constant TRUE = 2;

    /// @dev Max number of logged days of a pool to be processed in a single batch
    uint256 private constant BATCH_DAY_COUNT = 2 * (365 + 1);

    ////////////////////////////
    //    State Variables     //
    ////////////////////////////

    /// @dev Addresses of stakeholders stored
    EnumerableSet.AddressSet private s_stakeholders;

    /// @dev Stores the amount each stakeholder has stakes in each pool
    mapping(address => uint256[NUM_POOLS])
        private s_stakedPerStakeholderAndPool;

    /// @dev How much each stakeholder has staked in total
    mapping(address => uint256) private s_stakedOf;

    /// @dev How many logs already exist for each pool
    uint256[NUM_POOLS] private s_existingPoolLogCount;

    /// @dev Stakes stored
    mapping(uint256 => Stake) private s_stakes;

    /// @dev Incremental counter of stakes, applied as stakeId when creating new stake
    uint256 public s_nextStakeId;

    /// @dev total number of staked ZND tokens
    uint256 public s_totalStaked;

    /// @dev a flag to make the start of reward computation callable only once ever
    uint256 private s_rewardComputationStarted;

    /// @dev timestamp at which the next reward can be computed
    uint256 private s_nextValidRewardComputationTime;

    /// @dev staked per tier and plan
    uint256[NUM_POOLS] private s_stakedPerPool;

    /// @dev Collected pool logs since last comprehensive reward distribution for each pool
    mapping(uint256 => PoolLog[]) private s_poolLogs;

    /// @dev Timestamp at which the last pool log was created
    uint256 private s_lastPoolLogCreationTime;

    /// @dev Amount of ZND tokens rewarded per stakeholder
    mapping(address => Reward) private s_rewardOf;

    /// @dev Number of minutes to move reset point
    uint256 private s_resetPointOffset;

    /// @dev Amount of rewards for daily distribution
    uint256 public s_dailyReward;

    ///////////////////////////////
    //         Constructor       //
    ///////////////////////////////

    constructor(
        address _zndPlatform,
        address _discretionaryWithdrawalAccount,
        address _overLimitWithdrawalAccount,
        uint256 _initialDailyReward,
        uint256 _initialDiscretionaryPoolBalance,
        uint256 _initialRewardsPoolBalance,
        VestingPayload[] memory _vestingPayload
    )
        ZNDTreasury(
            _zndPlatform,
            _discretionaryWithdrawalAccount,
            _overLimitWithdrawalAccount,
            _initialDiscretionaryPoolBalance,
            _initialRewardsPoolBalance,
            _vestingPayload
        )
        EIP712("ZNDStaking", "1")
    {
        if (_zndPlatform == address(0)) revert Znd_ZeroAddress();

        s_zndPlatform = _zndPlatform;

        s_nextStakeId = 1; // stakeIds starts from 1 to avoid mistaking for nonexistent value

        s_totalStaked = 0;

        s_dailyReward = _initialDailyReward;

        s_resetPointOffset = 0;

        s_rewardComputationStarted = FALSE;
    }

    ////////////////////////////////////////
    //      Staking and Withdrawal        //
    ////////////////////////////////////////

    /// @inheritdoc IZNDStaking
    function stakeCentral(
        StakePayload[] calldata _stakes
    ) external onlyCentralPlatform returns (bool) {
        uint256 numberOfStakes = _stakes.length;

        // check if there's anything to stake
        if (numberOfStakes == 0) revert Stake_NoStakesProvided();

        // check for empty stakes
        for (uint256 i = 0; i < numberOfStakes; i++) {
            if (_stakes[i].amount == 0)
                revert Stake_ZeroAmountStakeNotAllowed();
        }

        distributeRewards();

        // if all the pool logs haven't been processed, return
        if (
            s_stakeholders.contains(msg.sender) &&
            s_lastPoolLogCreationTime >
            s_rewardOf[msg.sender].lastDistributionTime
        ) {
            emit DidNotDistributeAllRewardsToSelf(msg.sender);
            return false;
        }

        // set up local variables to track the staked amount and stake ids
        uint256 stakedInBatch = 0;
        uint256[] memory createdStakeIds = new uint256[](numberOfStakes);

        // loop through the array, stake tokens, and track stake ids and total staked amount
        for (uint256 i = 0; i < numberOfStakes; i++) {
            StakePayload calldata stake = _stakes[i];
            stakedInBatch += stake.amount;

            createdStakeIds[i] = _stakeTokens(stake, i);
        }

        // set next stake id
        s_nextStakeId += numberOfStakes;

        // increase total staked amount
        s_totalStaked += stakedInBatch;

        emit StakedCentral(createdStakeIds, _stakes.length, stakedInBatch);

        // transfer funds from central platform to this contract
        IERC20(s_zndToken).safeTransferFrom(
            msg.sender,
            address(this),
            stakedInBatch
        );

        return true;
    }

    /// @inheritdoc IZNDStaking
    function stakeEOA(
        uint256 _plan,
        uint256 _amount,
        bytes calldata _signature
    ) external onlyEOA returns (bool) {
        // revert if nothing to stake
        if (_amount == 0) revert Stake_ZeroAmountStakeNotAllowed();

        // check if the message has been signed by the message sender
        bytes32 digest = _hashTypedDataV4(
            keccak256(abi.encode(_STAKE_EOA_TYPEHASH, _plan, _amount))
        );
        address signer = digest.recover(_signature);
        if (signer != msg.sender) revert Znd_InvalidSigner();

        // calls compute rewards distribution to take into account changes from last computation until now
        // that way the consistency of the system state and rewards distribution is preserved
        distributeRewards();

        // if all the pool logs haven't been processed, return
        if (
            s_stakeholders.contains(msg.sender) &&
            s_lastPoolLogCreationTime >
            s_rewardOf[msg.sender].lastDistributionTime
        ) {
            emit DidNotDistributeAllRewardsToSelf(msg.sender);
            return false;
        }

        // the stake will be placed in one of 4 basic tier pools according to specified plan
        Pool memory pool = Pool({tier: Tier.Basic, plan: Plan(_plan)});

        // stake tokens
        StakePayload memory stakePayload = StakePayload({
            amount: _amount,
            tier: uint256(pool.tier),
            plan: uint256(pool.plan)
        });
        uint256 stakeId = _stakeTokens(stakePayload, 0);

        // increase total staked amount
        s_totalStaked += _amount;

        // set next stake id
        s_nextStakeId += 1;

        emit StakedEOA(stakeId, msg.sender, _amount, Plan(_plan));

        // transfer tokens from the EOA to this contract
        IERC20(s_zndToken).safeTransferFrom(msg.sender, address(this), _amount);

        return true;
    }

    /// @inheritdoc IZNDStaking
    function withdrawStakesCentral(
        WithdrawCentralPayload[] calldata _withdrawals
    ) external onlyCentralPlatform returns (bool) {
        uint256 numberOfWithdrawals = _withdrawals.length;

        // check if there's anything in the provided array and revert if not
        if (numberOfWithdrawals == 0) revert Stake_NoWithdrawalsProvided();

        // check for empty stakes
        for (uint256 i = 0; i < numberOfWithdrawals; i++) {
            if (_withdrawals[i].amount == 0)
                revert Stake_ZeroAmountWithdrawalNotAllowed();
        }

        // calls compute rewards distribution to take into account changes from last computation until now
        // that way the consistency of the system state and rewards distribution is preserved
        distributeRewards();

        // if all the pool logs haven't been processed, return
        if (
            s_stakeholders.contains(msg.sender) &&
            s_lastPoolLogCreationTime >
            s_rewardOf[msg.sender].lastDistributionTime
        ) {
            emit DidNotDistributeAllRewardsToSelf(msg.sender);
            return false;
        }

        // set up local variables that track ids of withdrawn stakes, withdrawn amount,
        // and applied fees for early withdrawal
        uint256[] memory stakeIds = new uint256[](numberOfWithdrawals);
        uint256 penaltyFees = 0;
        uint256 totalAmountToWithdraw = 0;

        // loop through the provided array and execute withdrawals
        for (uint256 i = 0; i < numberOfWithdrawals; i++) {
            WithdrawCentralPayload calldata withdrawal = _withdrawals[i];

            (uint256 amountToWithdraw, uint256 penaltyFee) = _withdrawTokens(
                withdrawal.stakeId,
                withdrawal.amount
            );

            stakeIds[i] = withdrawal.stakeId;

            penaltyFees += penaltyFee;

            totalAmountToWithdraw += amountToWithdraw;
        }

        // decrease total staked for the total amount to withdraw and applied penalty fees
        s_totalStaked -= totalAmountToWithdraw + penaltyFees;

        // increase balance of the fees pool
        s_feesPoolBalance += penaltyFees;

        emit WithdrawStakesCentral(
            stakeIds,
            stakeIds.length,
            totalAmountToWithdraw,
            penaltyFees
        );

        // transfer tokens to the central platform
        IERC20(s_zndToken).safeTransfer(msg.sender, totalAmountToWithdraw);

        return true;
    }

    /// @inheritdoc IZNDStaking
    function withdrawStakeEOA(
        uint256 _stakeId,
        uint256 _amount,
        bytes calldata _signature
    ) external onlyEOA returns (bool) {
        // revert if nothing to stake
        if (_amount == 0) revert Stake_ZeroAmountWithdrawalNotAllowed();

        // check if the message has been signed by the message sender
        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(_WITHDRAW_STAKE_EOA_TYPEHASH, _stakeId, _amount)
            )
        );
        address signer = digest.recover(_signature);
        if (signer != msg.sender) revert Znd_InvalidSigner();

        // calls compute rewards distribution to take into account changes from last computation until now
        // that way the consistency of the system state and rewards distribution is preserved
        distributeRewards();

        // if all the pool logs haven't been processed, return
        if (
            s_stakeholders.contains(msg.sender) &&
            s_lastPoolLogCreationTime >
            s_rewardOf[msg.sender].lastDistributionTime
        ) {
            emit DidNotDistributeAllRewardsToSelf(msg.sender);
            return false;
        }

        // withdraw tokens and collect how much to withdraw and applied penalty for early withdrawal
        (uint256 amountToWithdraw, uint256 penaltyFee) = _withdrawTokens(
            _stakeId,
            _amount
        );

        // decrease total staked for the amount to withdraw and applied penalty fee
        s_totalStaked -= amountToWithdraw + penaltyFee;

        // increase balance of the fees pool
        s_feesPoolBalance += penaltyFee;

        emit WithdrawStakeEOA(
            _stakeId,
            msg.sender,
            amountToWithdraw,
            penaltyFee
        );

        // transfer tokens from this contract to the EOA
        IERC20(s_zndToken).safeTransfer(msg.sender, amountToWithdraw);

        return true;
    }

    ////////////////////////////////////////////////////
    //      Rewards Computation and Withdrawal        //
    ////////////////////////////////////////////////////

    /// @inheritdoc IZNDStaking
    function startRewardComputation() external onlyCentralPlatform {
        if (TRUE == s_rewardComputationStarted)
            revert Reward_ComputationAlreadyStarted();

        s_rewardComputationStarted = TRUE;
        s_nextValidRewardComputationTime = block.timestamp;

        emit RewardComputationStarted();
    }

    /// @inheritdoc IZNDStaking
    function withdrawRewardsEOA(
        uint256 _amount,
        bytes calldata _signature
    ) external onlyEOA {
        // check if amount to withdraw is greater than 0
        if (_amount == 0) revert Reward_ZeroAmountWithdrawalNotAllowed();

        // check if the message has been signed by the message sender
        bytes32 digest = _hashTypedDataV4(
            keccak256(abi.encode(_WITHDRAW_REWARDS_EOA_TYPEHASH, _amount))
        );
        address signer = digest.recover(_signature);
        if (signer != msg.sender) revert Znd_InvalidSigner();

        distributeRewards();

        // if all the pool logs haven't been processed, return
        if (
            s_stakeholders.contains(msg.sender) &&
            s_lastPoolLogCreationTime >
            s_rewardOf[msg.sender].lastDistributionTime
        ) {
            emit DidNotDistributeAllRewardsToSelf(msg.sender);
            return;
        }

        // check if the amount is not higher than available to caller
        if (_amount > s_rewardOf[msg.sender].amount)
            revert Reward_WithdrawInsufficientFunds();

        // decrease amount of rewards caller posses
        s_rewardOf[msg.sender].amount -= _amount;

        // decrease amount of rewards available in the rewards pool
        if (s_rewardsPoolBalance < _amount) {
            revert Reward_InsufficientFundsInPool();
        }
        s_rewardsPoolBalance -= _amount;

        emit WithdrawRewardsEOA(msg.sender, _amount);

        // send tokens to the caller
        IERC20(s_zndToken).safeTransfer(msg.sender, _amount);
    }

    /// @inheritdoc IZNDStaking
    function withdrawRewardsCentral(
        uint256 _amount
    ) external onlyCentralPlatform {
        // check if amount to withdraw is greater than 0
        if (_amount == 0) revert Reward_ZeroAmountWithdrawalNotAllowed();

        distributeRewards();

        // if all the pool logs haven't been processed, return
        if (
            s_stakeholders.contains(msg.sender) &&
            s_lastPoolLogCreationTime >
            s_rewardOf[msg.sender].lastDistributionTime
        ) {
            emit DidNotDistributeAllRewardsToSelf(msg.sender);
            return;
        }

        // check if the amount is not higher than available to caller
        if (_amount > s_rewardOf[msg.sender].amount)
            revert Reward_WithdrawInsufficientFunds();

        // decrease amount of rewards caller posses
        s_rewardOf[msg.sender].amount -= _amount;

        // decrease amount of rewards available in the rewards pool
        if (s_rewardsPoolBalance < _amount) {
            revert Reward_InsufficientFundsInPool();
        }
        s_rewardsPoolBalance -= _amount;

        emit WithdrawRewardsCentral(_amount);

        // send tokens to the caller
        IERC20(s_zndToken).safeTransfer(msg.sender, _amount);
    }

    /// @inheritdoc IZNDStaking
    function areAllRewardsDistributed() external view returns (bool) {
        return
            s_rewardOf[msg.sender].lastDistributionTime >=
            s_lastPoolLogCreationTime;
    }

    /**
     * @notice Distributes all the uncollected rewards to caller based on pool logs.
     * @dev It's being called internally by token balance affecting functionalities:
     * @dev staking or withdrawal or tokens (central or EOA), reward withdrawals (central and EOA).
     * @dev It's necessary to distribute all the rewards to self before proceeding with these functionalities.
     * @dev If the caller has been inactive for extensive periods of time, which would be more
     * @dev than 2 years of inactivity, the token balance affecting functionalities will emit
     * @dev `DidNotDistributeAllRewardsToSelf` event, this means that this functionality has to be invoked explicitly
     * @dev sufficient amount of times, by the stakeholder, in order for to collect all the rewards first.
     * @dev Invoking it sufficient amount of times can be easily achieved by calling it in a loop until
     * @dev `areAllRewardsDistributed` returns true, caller can then use the token balance affecting functionalities again.
     */
    function distributeRewards() public {
        // include all the new logs that happened in the meantime
        _computeReward();

        // if the caller is not a stakeholder return
        if (!s_stakeholders.contains(msg.sender)) return;

        // if the caller has already collected all of the rewards return
        if (
            s_rewardOf[msg.sender].lastDistributionTime >=
            s_lastPoolLogCreationTime
        ) return;

        // convert to local variable to save up on gas cost
        uint256[NUM_POOLS]
            memory stakedByStakeholderPerPool = s_stakedPerStakeholderAndPool[
                msg.sender
            ];

        // local variable to keep track of how much reward the stakeholder has collected
        uint256 newlyCollectedRewards = 0;

        // flag to know if the all the logs have been processed or not
        // in order to know if the last reward distribution time should be modified
        uint256 allLogsProcessed = TRUE;

        // this loop will process at most `BATCH_DAY_COUNT` logs of each pool
        // the stakeholder has funds in and collect the rewards for the processed logs
        for (uint256 plan = 0; plan < NUM_PLANS; plan++) {
            for (uint256 tier = 0; tier < NUM_TIERS; tier++) {
                // calculates linearized index of pool
                uint256 poolIndex = plan * NUM_TIERS + tier;

                uint256 stakeholderFundsInPool = stakedByStakeholderPerPool[
                    poolIndex
                ];

                // if stakeholder has nothing in pool, it will not affect reward
                if (0 == stakeholderFundsInPool) continue;

                // get the pool log range that should be processed
                uint256 poolStartIdx = s_rewardOf[msg.sender].startIdxPerPool[
                    poolIndex
                ];
                uint256 poolEndIdx = poolStartIdx + BATCH_DAY_COUNT;

                if (poolEndIdx >= s_poolLogs[poolIndex].length) {
                    // if it is the last batch, prevent out of bounds access
                    poolEndIdx = s_poolLogs[poolIndex].length;
                } else {
                    // otherwise not all logs can be processed so set the flag to false
                    allLogsProcessed = FALSE;
                }

                for (uint256 i = poolStartIdx; i < poolEndIdx; i++) {
                    // for the current pool log add the reward to the stakeholder based
                    // on the partition of his funds in the pool's total funds
                    newlyCollectedRewards +=
                        (stakeholderFundsInPool *
                            s_poolLogs[poolIndex][i].poolReward) /
                        s_poolLogs[poolIndex][i].stakedInPool;
                }

                // update the index of the next log to read to the log after current last
                s_rewardOf[msg.sender].startIdxPerPool[poolIndex] = poolEndIdx;
            }
        }

        // if all the logs have been processed
        // update the last distribution time to the current time,
        // otherwise do not update distribution time as there are
        // more rewards to be distributed to the stakeholder
        if (TRUE == allLogsProcessed) {
            s_rewardOf[msg.sender].lastDistributionTime = block.timestamp;
        }

        // add the newly collected rewards to the stakeholders rewards
        s_rewardOf[msg.sender].amount += newlyCollectedRewards;

        // emit appropriate event
        emit DistributedDailyReward(msg.sender);
    }

    /**
     * @notice Distributes specific the uncollected rewards to caller based on pool logs, based on parameters
     * @param _user the stakeholder for whom the rewards will be computed
     * @param _plan the plan for whitch the reward will be computed
     * @param _tier the tier for whitch the reward will be computed
     * @param _noLogs the maximum number of logs to go through
     * @dev It is very similar to the distributeRewards, with the difference being
     * @dev that it only distributes a specific number of rewards for a specific user in a specific pool
     */
    function distributeRewardsSpecific(
        address _user,
        uint256 _plan,
        uint256 _tier,
        uint256 _noLogs
    ) public {
        // include all the new logs that happened in the meantime
        _computeReward();

        // if the caller is not a stakeholder return
        if (!s_stakeholders.contains(_user)) return;

        // if the caller has already collected all of the rewards return
        if (s_rewardOf[_user].lastDistributionTime >= s_lastPoolLogCreationTime)
            return;

        // convert to local variable to save up on gas cost
        uint256[NUM_POOLS]
            memory stakedByStakeholderPerPool = s_stakedPerStakeholderAndPool[
                _user
            ];

        // local variable to keep track of how much reward the stakeholder has collected
        uint256 newlyCollectedRewards = 0;

        // calculates linearized index of pool
        uint256 poolIndex = _plan * NUM_TIERS + _tier;

        uint256 stakeholderFundsInPool = stakedByStakeholderPerPool[poolIndex];

        // if stakeholder has nothing in pool, it will not affect reward
        if (0 != stakeholderFundsInPool) {
            // get the pool log range that should be processed
            uint256 poolStartIdx = s_rewardOf[_user].startIdxPerPool[poolIndex];
            uint256 poolEndIdx = poolStartIdx + _noLogs;

            if (poolEndIdx >= s_poolLogs[poolIndex].length) {
                // if it is the last batch, prevent out of bounds access
                poolEndIdx = s_poolLogs[poolIndex].length;
            }

            for (uint256 i = poolStartIdx; i < poolEndIdx; i++) {
                // for the current pool log add the reward to the stakeholder based
                // on the partition of his funds in the pool's total funds
                newlyCollectedRewards +=
                    (stakeholderFundsInPool *
                        s_poolLogs[poolIndex][i].poolReward) /
                    s_poolLogs[poolIndex][i].stakedInPool;
            }

            // update the index of the next log to read to the log after current last
            s_rewardOf[_user].startIdxPerPool[poolIndex] = poolEndIdx;
        }

        // add the newly collected rewards to the stakeholders rewards
        s_rewardOf[_user].amount += newlyCollectedRewards;

        // emit appropriate event
        emit DistributedDailyReward(_user);
    }

    /////////////////////////////////////
    //               Getters           //
    /////////////////////////////////////

    /// @inheritdoc IZNDStaking
    function getStake(uint256 _stakeId) external view returns (Stake memory) {
        Stake storage stake = s_stakes[_stakeId];

        if (stake.stakeholder == address(0)) revert Stake_DoesNotExist();

        return stake;
    }

    /// @inheritdoc IZNDStaking
    function getRewardAmount() external view returns (uint256) {
        if (!s_stakeholders.contains(msg.sender))
            revert Reward_NotAStakeholder();

        return s_rewardOf[msg.sender].amount;
    }

    /// @inheritdoc IZNDStaking
    function getRewardAmountOfAddress(
        address _address
    ) external view onlyCentralPlatform returns (uint256) {
        return s_rewardOf[_address].amount;
    }

    /// @inheritdoc IZNDStaking
    function getAmountStakedInPool(
        Tier _tier,
        Plan _plan
    ) external view returns (uint256) {
        return s_stakedPerPool[uint256(_plan) * NUM_TIERS + uint256(_tier)];
    }

    /////////////////////////////////////
    //               Setters           //
    /////////////////////////////////////

    /// @inheritdoc IZNDStaking
    function setDailyReward(uint256 _amount) external onlyCentralPlatform {
        _computeReward();

        s_dailyReward = _amount;

        emit DailyRewardChanged(_amount);
    }

    /// @inheritdoc IZNDStaking
    function setResetPointOffset(
        uint256 _newOffset
    ) external onlyCentralPlatform {
        if (s_totalStaked > 0) revert Parameters_PoolsAreNotEmpty();

        s_resetPointOffset = _newOffset;

        emit DailyRewardOffsetChanged(_newOffset);
    }

    //////////////////////////
    //  Internal Functions  //
    //////////////////////////

    // Creates any missing logs for all the pools based on the token stakings and withdrawals
    // that happened between the current block timestamp and `s_nextValidRewardComputationTime`.
    function _computeReward() internal {
        // if the computation has not been enabled by the platform, exit
        if (FALSE == s_rewardComputationStarted) return;

        // if already computed since the last reward computation reset, exit
        if (s_nextValidRewardComputationTime > block.timestamp) return;

        // if there is nothing to distribute, exit
        if (s_dailyReward == 0) return;

        // apply parameter changes if any have happened in the meantime
        _checkPendingBoostChanges();

        // calculates the number of rewards to be distributed,
        // in case some days have been missed (because the function
        // was not invoked), they will be calculated too
        uint256 rewardsToDistributeCount = 1 +
            (block.timestamp - s_nextValidRewardComputationTime) /
            1 days;

        // Calculates pool share and total share

        // set up local variables to track the total share of tokens in all pools,
        // share of tokens per pool, and reward per pool
        uint256 totalShare = 0;
        uint256[] memory poolShares = new uint256[](NUM_POOLS);
        uint256[NUM_POOLS] memory stakedPerPool = s_stakedPerPool;

        for (uint8 plan = 0; plan < NUM_PLANS; plan++) {
            for (uint8 tier = 0; tier < NUM_TIERS; tier++) {
                // calculates linearized index of pool
                uint256 poolIndex = plan * NUM_TIERS + tier;

                // if the pool is empty then the shares are also 0
                if (0 == stakedPerPool[poolIndex]) continue;

                // apply pool bonuses to the number of tokens
                uint256 sharesInPool = (stakedPerPool[poolIndex] *
                    s_planBoost[plan] *
                    s_tierBoost[tier]) / 10000;

                // save share calculation of the current pool
                poolShares[poolIndex] = sharesInPool;

                // add share of the current pool to the total share
                totalShare += sharesInPool;
            }
        }

        // if all shares are 0, there is no need to continue,
        // as no stakes will result in no rewards, just update
        // the timestamp for the next computation
        if (totalShare == 0) {
            // `block.timestamp - (block.timestamp % 1 days)` is midnight of the current day,
            // eg: if now is 10:36 am, this will be 10 hours and 36 minutes earlier
            // `+1 days` gets the next midnight, and then the offset is applied
            s_nextValidRewardComputationTime =
                block.timestamp -
                (block.timestamp % 1 days) +
                1 days +
                s_resetPointOffset;

            return;
        }

        // convert to a local variable to save up on gas costs
        uint256 dailyReward = s_dailyReward;

        // Log creation

        // stores a new pool log for each pool
        for (uint8 plan = 0; plan < NUM_PLANS; plan++) {
            for (uint8 tier = 0; tier < NUM_TIERS; tier++) {
                uint256 poolIndex = plan * NUM_TIERS + tier;
                uint256 sharesInPool = poolShares[poolIndex];

                // if the pool has no shares then its log will also contain
                // no stakes and no reward, hence it can be skipped
                if (sharesInPool == 0) continue;

                PoolLog memory poolLog;

                // for each pool calculate its reward based on the daily reward,
                // number of rewards to be distributed and the participation of the
                // current pool share in total share
                poolLog.poolReward =
                    (dailyReward * rewardsToDistributeCount * sharesInPool) /
                    totalShare;
                poolLog.stakedInPool = stakedPerPool[poolIndex];

                // store the log
                s_poolLogs[poolIndex].push(poolLog);

                // increase the number of existing logs for the pool
                s_existingPoolLogCount[poolIndex]++;
            }
        }

        // store the new last log creation timestamp
        s_lastPoolLogCreationTime = block.timestamp;

        // math has already been explained in the same function a few lines above
        s_nextValidRewardComputationTime =
            block.timestamp -
            (block.timestamp % 1 days) +
            1 days +
            s_resetPointOffset;

        // emit appropriate event
        emit ComputedDailyRewards();
    }

    function _stakeTokens(
        StakePayload memory _payload,
        uint256 _index
    ) internal returns (uint256 stakeId) {
        // create appropriate pool
        Pool memory pool = Pool({
            tier: Tier(_payload.tier),
            plan: Plan(_payload.plan)
        });

        // calculate stake id for the stake
        stakeId = s_nextStakeId + _index;

        // create and store Stake struct
        s_stakes[stakeId] = Stake(
            stakeId,
            _payload.amount,
            block.timestamp,
            msg.sender,
            pool
        );

        // increase the amount staked by the stakeholder in total
        s_stakedOf[msg.sender] += _payload.amount;

        // increase amount staked in appropriate pool
        s_stakedPerPool[_poolToIndex(pool)] += _payload.amount;

        // increase the amount staked by the stakeholder in appropriate pool
        s_stakedPerStakeholderAndPool[msg.sender][
            _poolToIndex(pool)
        ] += _payload.amount;

        // add stakeholder to the set of stakeholder if not already present
        if (s_stakeholders.add(msg.sender)) {
            s_rewardOf[msg.sender].startIdxPerPool = s_existingPoolLogCount;
            s_rewardOf[msg.sender].lastDistributionTime = block.timestamp;
        }

        return stakeId;
    }

    function _withdrawTokens(
        uint256 _stakeId,
        uint256 _amount
    ) internal returns (uint256 amountToWithdraw, uint256 penaltyFee) {
        // Check if any changes to parametrs should take effect
        _checkPendingPenaltyChanges();

        // storage pointer to the stake specified by stake id
        Stake storage stake = s_stakes[_stakeId];

        // revert if stake doesn't exist
        if (stake.stakeholder == address(0)) revert Stake_DoesNotExist();

        // revert if try to withdraw more than staked
        if (stake.amount < _amount) revert Stake_WithdrawInsufficientFunds();

        // revert if message sender is not owner of the stake
        if (msg.sender != stake.stakeholder) revert Stake_NotOwner();

        // set up local variables to track amount to withdraw and penalty fees for early withdrawal
        penaltyFee = 0;
        amountToWithdraw = _amount;

        // check if it's an early withdrawal
        if (_shouldApplyPenalty(stake.stakedAt, stake.pool.plan)) {
            // if yes apply penalty fee based on staking plan
            penaltyFee =
                (amountToWithdraw * s_penaltyFee[uint256(stake.pool.plan)]) /
                10_000;

            // lower the amount that will be withdrawn by penalty fee
            amountToWithdraw -= penaltyFee;
        }

        // decrease the amount staked by the stakeholder in total
        s_stakedOf[msg.sender] -= _amount;

        // decrease the amount of tokens staked in the pool from which withdraw
        s_stakedPerPool[_poolToIndex(stake.pool)] -= _amount;

        // decrease the amount staked by the stakeholder in appropriate pool
        s_stakedPerStakeholderAndPool[msg.sender][
            _poolToIndex(stake.pool)
        ] -= _amount;

        // if stakeholder does not have any funds staked anymore, remove the stakeholder
        // wrapped in require because of static analysis
        if (0 == s_stakedOf[msg.sender]) {
            require(s_stakeholders.remove(msg.sender));
        }

        // decrease amount of staked in the stake that's withdrawn from
        s_stakes[_stakeId].amount -= _amount;

        // if staked amount in stake is dropped to 0
        if (s_stakes[_stakeId].amount == 0) {
            // and delete stake struct
            delete s_stakes[_stakeId];
        }

        return (amountToWithdraw, penaltyFee);
    }

    // Function to check if a given timestamp is more than 'periodInDays' days ago.
    // In that case penalty should be applied.
    function _shouldApplyPenalty(
        uint256 timestampToCheck,
        Plan plan
    ) internal view returns (bool) {
        uint256 currentTimestamp = block.timestamp;

        uint256 periodInDays = s_planDuration[uint256(plan)];

        uint256 daysAgoTimestamp = currentTimestamp - (periodInDays * 1 days);

        return (timestampToCheck > daysAgoTimestamp);
    }

    function _poolToIndex(Pool memory pool) internal pure returns (uint256) {
        return uint256(pool.plan) * NUM_TIERS + uint256(pool.tier);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.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 Ownable is Context {
    address private _owner;

    /**
     * @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.
     */
    constructor(address initialOwner) {
        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) {
        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 {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {Ownable} from "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

File 5 of 32 : IERC5267.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)

pragma solidity ^0.8.20;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Burnable.sol)

pragma solidity ^0.8.20;

import {ERC20} from "../ERC20.sol";
import {Context} from "../../../utils/Context.sol";

/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
abstract contract ERC20Burnable is Context, ERC20 {
    /**
     * @dev Destroys a `value` amount of tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 value) public virtual {
        _burn(_msgSender(), value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, deducting from
     * the caller's allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `value`.
     */
    function burnFrom(address account, uint256 value) public virtual {
        _spendAllowance(account, _msgSender(), value);
        _burn(account, value);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.20;

import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";

/**
 * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
    bytes32 private constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /**
     * @dev Permit deadline has expired.
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @dev Mismatched signature.
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /**
     * @inheritdoc IERC20Permit
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        _approve(owner, spender, value);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
        return _domainSeparatorV4();
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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 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 calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError, bytes32) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.20;

import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    ShortString private immutable _name;
    ShortString private immutable _version;
    string private _nameFallback;
    string private _versionFallback;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _name = name.toShortStringWithFallback(_nameFallback);
        _version = version.toShortStringWithFallback(_versionFallback);
        _hashedName = keccak256(bytes(name));
        _hashedVersion = keccak256(bytes(version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
            return _cachedDomainSeparator;
        } else {
            return _buildDomainSeparator();
        }
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev See {IERC-5267}.
     */
    function eip712Domain()
        public
        view
        virtual
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _name which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Name() internal view returns (string memory) {
        return _name.toStringWithFallback(_nameFallback);
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _version which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Version() internal view returns (string memory) {
        return _version.toStringWithFallback(_versionFallback);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)

pragma solidity ^0.8.20;

import {Strings} from "../Strings.sol";

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

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

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

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

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

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

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
pragma solidity ^0.8.20;

/**
 * @dev Provides tracking nonces for addresses. Nonces will only increment.
 */
abstract contract Nonces {
    /**
     * @dev The nonce used for an `account` is not the expected current nonce.
     */
    error InvalidAccountNonce(address account, uint256 currentNonce);

    mapping(address account => uint256) private _nonces;

    /**
     * @dev Returns the next unused nonce for an address.
     */
    function nonces(address owner) public view virtual returns (uint256) {
        return _nonces[owner];
    }

    /**
     * @dev Consumes a nonce.
     *
     * Returns the current value and increments nonce.
     */
    function _useNonce(address owner) internal virtual returns (uint256) {
        // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
        // decremented or reset. This guarantees that the nonce never overflows.
        unchecked {
            // It is important to do x++ and not ++x here.
            return _nonces[owner]++;
        }
    }

    /**
     * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
     */
    function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
        uint256 current = _useNonce(owner);
        if (nonce != current) {
            revert InvalidAccountNonce(owner, current);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol)

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        /// @solidity memory-safe-assembly
        assembly {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using
     * {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

File 25 of 32 : IZNDBase.sol
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

/**
 * @notice Common type definitions and errors.
 */
interface IZNDBase {
    ///////////////////////////
    //   Type Declarations   //
    ///////////////////////////

    /// @dev 6 possible tiers central platform users can achieve, external users belongs to basic tier
    enum Tier {
        Basic,
        Silver,
        Gold,
        Platinum,
        Diamond,
        Ambassador
    }

    /// @dev 4 possible duration options for staking tokens
    enum Plan {
        ThirtyDays,
        NinetyDays,
        HundredEightyDays,
        ThreeHundredSixtyDays
    }

    ////////////////
    //   Errors   //
    ////////////////

    error Znd_NotEOA();
    error Znd_NotZNDPlatform();
    error Znd_ZeroAddress();
}

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {IZNDBase} from "./IZNDBase.sol";

/**
* @notice Interface for staking parameters change functionality.
*/
interface IZNDParameters is IZNDBase {
    ///////////////////////////
    //   Type Declarations   //
    ///////////////////////////

    /// @dev Pending change to tier boost that still hasn't passed it's grace period
    struct TierBoostChange {
        /// @dev tier for which we are changing the boost amount
        uint256 tier;
        /// @dev the value to be changed to when the grace period ends
        uint256 newValue;
        /// @dev block timestamp of when the change was initiated
        uint256 initiated;
    }

    // @dev Pending change to plan boost that still hasn't passed it's grace period
    struct PlanBoostChange {
        /// @dev plan for which we are changing the boost amount
        uint256 plan;
        /// @dev the value to be changed to when the grace period ends
        uint256 newValue;
        /// @dev block timestamp of when the change was initiated
        uint256 initiated;
    }

    // @dev Pending change to penalty fee that still hasn't passed it's grace period
    struct PenaltyChange {
        /// @dev plan for which we are changing the penalty fee
        uint256 plan;
        /// @dev the value to be changed to when the grace period ends
        uint256 newValue;
        /// @dev block timestamp of when the change was initiated
        uint256 initiated;
    }

    ////////////////
    //   Errors   //
    ////////////////

    error Parameters_CooldownNotOver();
    error Parameters_ChangeNotWithinBounds();
    error Parameters_PlanOrTierNotValid();

    ////////////////
    //   Events   //
    ////////////////

    event ParameterChangeRequested(
        string changeType,
        uint256 planOrTier,
        uint256 oldValue,
        uint256 newValue
    );

    ////////////////////////////////////
    //      Parameters Change         //
    ////////////////////////////////////

    /**
     * @notice Initialise the change of tier boost to be set after the grace period
	 * @param _tier tier for which we are changing the boost
	 * @param _newValue the new value to be changed to
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
	*/
    function changeTierBoost(uint256 _tier, uint256 _newValue) external;

    /**
     * @notice Initialise the change of plan boost to be set after the grace period
	 * @param _plan plan for which we are changing the boost
	 * @param _newValue the new value to be changed to
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
	*/
    function changePlanBoost(uint256 _plan, uint256 _newValue) external;

    /**
     * @notice Initialise the change of penalty fee for a specific plan to be set after the grace period
	 * @param _plan plan for which we are changing the penalty fee
	 * @param _newValue the new value to be changed to
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
	*/
    function changePenaltyFee(uint256 _plan, uint256 _newValue) external;
}

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {IZNDTreasury} from "./IZNDTreasury.sol";

/**
 * @notice Interface for staking functionality.
 */
interface IZNDStaking is IZNDTreasury {
    ///////////////////////////
    //   Type Declarations   //
    ///////////////////////////

    /// @dev a pool that consists of tier and plan, there can be 24 of pools
    struct Pool {
        Tier tier;
        Plan plan;
    }

    /// @dev payload that describes single stake by providing amount to be staked, tier and plan
    /// @dev used only for central platform staking
    struct StakePayload {
        uint256 amount;
        uint256 tier;
        uint256 plan;
    }

    /// @dev payload that describes signle withdraw intention by providing stake ID and withdraw amount
    /// @dev used only for central platform withdraw
    struct WithdrawCentralPayload {
        uint256 stakeId;
        uint256 amount;
    }

    /// @dev a single stake stored in contract
    struct Stake {
        /// @dev incremental field that uniquely identify stake
        uint256 stakeId;
        /// @dev amount of tokens staked
        uint256 amount;
        /// @dev timestamp when tokens are staked
        uint256 stakedAt;
        /// @dev address that staked tokens, can be the central platform or an EOA
        address stakeholder;
        /// @dev a pool tokens are staked into
        Pool pool;
    }

    /// @dev a single pool log stored after every computation
    struct PoolLog {
        /// @dev the amount of funds staked in pool at the time
        uint256 stakedInPool;
        /// @dev the amount of the reward the pool had at the time
        uint256 poolReward;
    }

    /// @dev reward information stored for each stakeholder
    struct Reward {
        /// @dev total amount of the reward the stakeholder has
        uint256 amount;
        /// @dev timestamp at which the reward was last distributed to the stakeholder
        uint256 lastDistributionTime;
        /// @dev stores the start index from which the distribution is going to start for each pool
        uint256[24] startIdxPerPool;
    }

    ////////////////
    //   Errors   //
    ////////////////

    error Znd_InvalidSigner();
    error Stake_ComprehensiveRewardDistributionOngoing();
    error Stake_NoStakesProvided();
    error Stake_NoWithdrawalsProvided();
    error Stake_NotOwner();
    error Stake_DoesNotExist();
    error Stake_WithdrawInsufficientFunds();
    error Stake_AddressNotFound();
    error Stake_ZeroAmountStakeNotAllowed();
    error Stake_ZeroAmountWithdrawalNotAllowed();
    error Reward_WithdrawInsufficientFunds();
    error Reward_ZeroAmountWithdrawalNotAllowed();
    error Reward_ComputationAlreadyStarted();
    error Reward_NotAStakeholder();
    error Reward_InsufficientFundsInPool();
    error Parameters_PoolsAreNotEmpty();

    ////////////////
    //   Events   //
    ////////////////

    event StakedCentral(
        uint256[] stakeIds,
        uint256 numberOfStakes,
        uint256 totalAmount
    );

    event StakedEOA(
        uint256 stakeId,
        address indexed stakeholder,
        uint256 amount,
        Plan plan
    );

    event RewardComputationStarted();

    event ComputedDailyRewards();

    event DistributedDailyReward(address indexed recipient);

    event BatchSizeChanged(uint256 newSize);

    event FinishedComprehensiveRewardDistribution();

    event WithdrawStakesCentral(
        uint256[] stakeIds,
        uint256 numberOfWithdrawals,
        uint256 totalAmount,
        uint256 totalPenalites
    );

    event WithdrawStakeEOA(
        uint256 indexed stakeId,
        address indexed stakeholder,
        uint256 amountWithdrawn,
        uint256 penatlyFee
    );

    event WithdrawRewardsCentral(uint256 amount);

    event WithdrawRewardsEOA(
        address indexed stakeholder,
        uint256 amountWithdrawn
    );

    event DiscretionaryPoolWithdrawal(
        address account,
        uint256 amount,
        uint256 limit
    );

    event DailyRewardOffsetChanged(uint256 newValue);

    event DailyRewardChanged(uint256 amount);

    event DidNotDistributeAllRewardsToSelf(address indexed stakeholder);

    ////////////////////////////////////////
    //      Staking and Withdrawal        //
    ////////////////////////////////////////

    /**
     * @notice Allows the centralized platform to create a batch of stakes in a single transaction.
     * @param _stakes array of StakePayload structs.
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
     * @dev Requires all amounts to be greater than 0, reverts with Stake_ZeroAmountStakeNotAllowed error otherwise.
     * @dev Requires that input array contains at least one element, reverts with Stake_NoStakesProvided error otherwise.
     * @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
     * @dev Emits StakedCentral event.
     * @dev Emits DidNotDistributeAllRewardsToSelf event.
     * @dev Transfers total amount of staked tokens from central platform to staking contract.
     * @dev Returns true if staking was successful and false if it failed
     */
    function stakeCentral(
        StakePayload[] calldata _stakes
    ) external returns (bool);

    /**
     * @notice Allows an EOA to create a stake in a Basic tier, with selected plan.
     * @param _plan Plan to be used for the stake.
     * @param _amount Amount of tokens to be staked.
     * @param _signature signature to validate the caller of the action and the validity of data.
     * @dev Requires to be called by EOA, reverts with Znd_NotEOA error otherwise.
     * @dev Requires amount to be greater than 0, reverts with Stake_ZeroAmountStakeNotAllowed error otherwise.
     * @dev Requires to match signature with message sender, reverts with Znd_InvalidSigner error otherwise.
     * @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
     * @dev Emits StakedCentral event.
     * @dev Emits DidNotDistributeAllRewardsToSelf event.
     * @dev Transfers specified amount of tokens from EOA to staking contract.
     * @dev Returns true if staking was successful and false if it failed
     */
    function stakeEOA(
        uint256 _plan,
        uint256 _amount,
        bytes calldata _signature
    ) external returns (bool);

    /**
     * @notice Allows the centralized platform to create a batch of withdrawals in a single transaction.
     * @param _withdrawals array of WithdrawCentralPayload objects describing the stakes to be withdrawn.
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
     * @dev Requires all amounts to be greater than 0, reverts with Stake_ZeroAmountWithdrawalNotAllowed error otherwise.
     * @dev Requires that input array contains at least one element, reverts with Stake_NoWithdrawalsProvided error otherwise.
     * @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
     * @dev Emits WithdrawStakesCentral event.
     * @dev Emits DidNotDistributeAllRewardsToSelf event.
     * @dev Transfers total amount of withdrawn tokens from staking contract to central platform.
     * @dev Returns true if withdrawing was successful and false if it failed
     */
    function withdrawStakesCentral(
        WithdrawCentralPayload[] calldata _withdrawals
    ) external returns (bool);

    /**
     * @notice Allows an EOA to withdraw funds from a stake up to staked amount.
     * @param _stakeId ID of stake to be withdrawn.
     * @param _amount amount of ZND tokens to withdraw.
     * @param _signature signature to validate the caller of the action and the validity of data.
     * @dev Requires to be called by EOA, reverts with Znd_NotEOA error otherwise.
     * @dev Requires amount to be greater than 0, reverts with Stake_ZeroAmountWithdrawalNotAllowed error otherwise.
     * @dev Requires to match signature with message sender, reverts with Znd_InvalidSigner error otherwise.
     * @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
     * @dev Emits WithdrawStakeEOA event.
     * @dev Emits DidNotDistributeAllRewardsToSelf event.
     * @dev Transfers specified amount of tokens from staking contract to the EOA.
     * @dev Returns true if withdrawing was successful and false if it failed
     */
    function withdrawStakeEOA(
        uint256 _stakeId,
        uint256 _amount,
        bytes calldata _signature
    ) external returns (bool);

    ////////////////////////////////////////////////////
    //      Rewards Distribution and Withdrawal       //
    ////////////////////////////////////////////////////

    /**
     * @notice Allows the centralized platform to mark the start of the reward computations.
     * @dev Requires that the caller is the central platform, will revert with Reward_NotCentralPlatform otherwise.
     * @dev Requires that the distribution has not already been started, will revert with Reward_ComputationAlreadyStarted otherwise.
     */
    function startRewardComputation() external;

    /**
     * @notice Allows an EOA to withdraw earned rewards up to the amount earned.
     * @param _amount amount of rewarded ZND tokens to withdraw.
     * @param _signature signature to validate the caller of the action and the validity of data.
     * @dev Requires to be called by an EOA, reverts with Znd_NotEOA error otherwise.
     * @dev Requires amount to be greater than 0, reverts with Reward_ZeroAmountWithdrawalNotAllowed error otherwise.
     * @dev Requires amount to be less or equal to rewards earned, reverts with Reward_WithdrawInsufficientFunds error otherwise.
     * @dev Requires to match signature with message sender, reverts with Znd_InvalidSigner error otherwise.
     * @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
     * @dev Emits WithdrawRewardsEOA event.
     * @dev Emits DidNotDistributeAllRewardsToSelf event.
     * @dev Transfers requested amount of tokens from staking contract to the EOA.
     */
    function withdrawRewardsEOA(
        uint256 _amount,
        bytes calldata _signature
    ) external;

    /**
     * @notice Allows the centralized platform to withdraw all rewards earned by central platform users.
     * @notice It will further distribute appropriate amounts based on user stakes.
     * @param _amount amount of rewarded ZND tokens to withdraw.
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
     * @dev Requires amount to be greater than 0, reverts with Reward_ZeroAmountWithdrawalNotAllowed error otherwise.
     * @dev Requires amount to be less or equal to rewards earned, reverts with Reward_WithdrawInsufficientFunds error otherwise.
     * @dev Requires that the comprehensive reward distribution is not ongoing, reverts with Stake_ComprehensiveRewardDistributionOngoing error otherwise.
     * @dev Emits WithdrawRewardsCentral event.
     * @dev Emits DidNotDistributeAllRewardsToSelf event.
     * @dev Transfers requested amount of tokens from staking contract to central platform.
     */
    function withdrawRewardsCentral(uint256 _amount) external;

    /**
     * @notice Returns a boolean that says if there are more rewards to distribute to the caller.
     * @dev If event `DidNotDistributeAllRewardsToSelf` has been emitted - after staking,
     * @dev withdrawing stake, or withdrawing reward (token balance affecting functionalities),
     * @dev the caller of the function is supposed to invoke this function in a loop until it returns true.
     * @dev Once all the rewards have been distributed, user can then use token balance affecting functionalities.
     */
    function areAllRewardsDistributed() external returns (bool);

    /////////////////////////////////////
    //               Setters           //
    /////////////////////////////////////

    /**
     * @notice Set the amount of rewards to be distributed daily.
     * @param _amount amount of rewards to be distributed daily.
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
     */
    function setDailyReward(uint256 _amount) external;

    /**
     * @notice Sets new offset (from UTC midnight) for reset point for reward computation.
     * @param _newOffset number of minutes to move reset point from UTC midnight.
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
     * @dev Requires all pools to be empty at the time of the offset change, reverts with Parameters_PoolAreNotEmpty error otherwise.
     */
    function setResetPointOffset(uint256 _newOffset) external;

    /////////////////////////////////////
    //               Getters           //
    /////////////////////////////////////

    /**
     * @notice Fetches a stake by its id.
     * @param _stakeId id of the stake.
     * @dev Requires stake to exist, otherwise revert with Stake_DoesNotExist error.
     * @dev Requires message sender to be stake owner, otherwise revert with Stake_NotOwner error.
     * @return Stake entity that represents the stake.
     */
    function getStake(uint256 _stakeId) external view returns (Stake memory);

    /**
     * @notice Gets amount of ZND tokens awarded to the caller.
     * @dev Requires to be called by an actual stakeholder, reverts with Reward_NotAStakeholder error otherwise.
     * @return amout of ZND tokens awarded to the caller address.
     */
    function getRewardAmount() external view returns (uint256);

    /**
     * @notice Gets amount of ZND tokens awarded to specified address.
     * @param _address address to get reward of.
     * @dev Requires to be called by central platform, reverts with Znd_NotZNDPlatform error otherwise.
     * @return amount of ZND tokens awarded to specified address.
     */
    function getRewardAmountOfAddress(
        address _address
    ) external view returns (uint256);

    /**
     * @notice Gets amount of ZND tokens staked in chosen pool.
     * @param _tier tier of the pool.
     * @param _plan plan of the pool.
     * @return amount of ZND tokens staked in the specified pool.
     */
    function getAmountStakedInPool(
        Tier _tier,
        Plan _plan
    ) external view returns (uint256);
}

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {IZNDParameters} from "./IZNDParameters.sol";

/**
 * @notice Interface for treasury functionality.
 */
interface IZNDTreasury is IZNDParameters {
    ///////////////////////////
    //   Type Declarations   //
    ///////////////////////////

    enum TreasuryPool {
        RewardsPool,
        DiscretionaryPool,
        FeesPool,
        VestingPool
    }

    /// @dev payload that describes vesting policy for a recipient.
    /// @dev every vesting, after the first one, is unlocked exactly 30 days after the previous one.
    struct VestingPayload {
        /// @dev wallet that is to receive the vesting
        address recipient;
        /// @dev the amount of funds to be unlocked over time
        uint256 quantityToUnlockOverTime;
        /// @dev the number of days over which the total amount will be unlocked
        uint256 numberOfDays;
        /// @dev time that needs to pass, since deployment, in order for first vesting to be unlocked
        uint256 cliffPeriodEnd;
        /// @dev the amount of funds to be unlocked initially
        uint256 initialUnlock;
    }

    /// @dev vesting data for a single recipient.
    struct VestingData {
        /// @dev the amount of unlocked available to be requested
        uint256 availableTokens;
        /// @dev the amount of times the recepient will receive their vesting
        uint256 numberOfUnlocksLeft;
        /// @dev the amount that is unlocked in a single vesting
        uint256 amountToReceivePerUnlock;
        /// @dev last time a vesting has been unlocked
        uint256 nextUnlockTimestamp;
    }

    ////////////////
    //   Errors   //
    ////////////////

    error Treasury__InvalidAddress();
    error Treasury__NotTreasuryAccount();
    error Treasury__OverLimitWithdrawalNotAllowed();
    error Treasury__AmountNotSpecified();
    error Treasury__InsufficientFunds();
    error Treasury__WithdrawalAccountCanNotBeOwner();
    error Treasury__WithdrawalAccountCanNotBeSpecialAccount();
    error Treasury__SpecialAccountCanNotBeOwner();
    error Treasury__SpecialAccountCanNotBeVestingRecipient();
    error Treasury__OwnerAccountCanNotBeVestingRecipient();
    error Treasury__RenouncingOwnershipIsDisabled();
    error Treasury__QuantityUnlockOverTimeNotDivisibleByNumberOfDays();
    error Treasury__DuplicateVestingAddress();

    ////////////////
    //   Events   //
    ////////////////

    event Treasury__Funding(
        address indexed funder,
        uint256 amount,
        TreasuryPool pool
    );

    event Treasury__Withdrawal(
        address indexed withdrawAccount,
        address indexed withdrawTo,
        uint256 amount,
        TreasuryPool pool
    );

    event Treasury__WithdrawalAccountUpdated(
        address indexed updater,
        address indexed newAccount
    );

    event Treasury__SpecialAccountUpdated(
        address indexed updater,
        address indexed newAccount
    );

    event Treasury__WithdrawalLimitUpdated(
        address indexed updater,
        uint256 newLimit
    );

    event Treasury__VestingPayoutCompleted(
        address indexed recipient,
        uint256 amount
    );

    /////////////////
    //   Funding   //
    /////////////////

    /**
     * @notice Send ZND tokens to rewards pool.
     * @param _amount the amount of tokens to add to the pool.
     * @dev emits Treasury__Funding event.
     */
    function fundRewardsPool(uint256 _amount) external;

    /**
     * @notice Send ZND tokens to discretionary pool.
     * @param _amount the amount of tokens to add to the pool.
     * @dev emits Treasury__Funding event.
     */
    function fundDiscretionaryPool(uint256 _amount) external;

    ///////////////////////////////////////////////////////
    //   Withdrawals from Discretionary and Fees pools   //
    ///////////////////////////////////////////////////////

    /**
     * @notice Withdraw ZND tokens from discretionary pool to provided account.
     * @param _to the address where to send tokens.
     * @param _amount the amount of tokens to withdraw from the pool.
     * @dev requires to be called by owner, regular account, or special account, reverts with Treasury__NotTreasuryAccount error otherwise.
     * @dev requires amount to be greater than 0, reverts with Treasury__AmountNotSpecified error otherwise.
     * @dev requires amount to be less than discretionary pool balance, reverts with Treasury__InsufficientFunds error otherwise.
     * @dev requires amount to be less or equal than withdrawal limit when withdraw with regular account,
     * @dev reverts with Treasury__OverLimitWithdrawalNotAllowed error otherwise.
     * @dev emits Treasury__Withdrawal event.
     */
    function withdrawFromDiscretionaryPool(
        address _to,
        uint256 _amount
    ) external;

    /**
     * @notice Withdraw ZND tokens from fees pool to provided account.
     * @param _to the address where to send tokens.
     * @param _amount the amount of tokens to withdraw from the pool.
     * @dev requires to be called by owner, regular account, or special account, reverts with Treasury__NotTreasuryAccount error otherwise.
     * @dev requires amount to be greater than 0, reverts with Treasury__AmountNotSpecified error otherwise.
     * @dev requires amount to be less than fees pool balance, reverts with Treasury__InsufficientFunds error otherwise.
     * @dev requires amount to be less or equal than withdrawal limit when withdraw with regular account,
     * @dev reverts with Treasury__OverLimitWithdrawalNotAllowed error otherwise.
     * @dev emits Treasury__Withdrawal event.
     */
    function withdrawFromFeesPool(address _to, uint256 _amount) external;

    ///////////////////////
    //   Vesting Payout  //
    ///////////////////////

    /**
     * @notice Dispenses funds to the recipient if the recipient is qualify to receive them.
     * @param _amount the amount of tokens to be dispensed to recipient.
     * @dev Transfers the tokens to the recipient and updates the state, if the recipient is qualified to receive the payout.
     * @dev The recipient qualifies to receive the requested amount if and only if sufficient amount of time has passed to
     * @dev receive it and they have enough tokens available in their vesting to receive it.
     * @dev Required to be called by a valid recipient address, reverts with Treasury__InvalidAddress error otherwise.
     * @dev Required that the amount be less or equal to the amount available for address, reverts with Treasury__InsufficientFunds error otherwise.
     * @dev Emits Treasury__VestingPayoutCompleted event.
     * @return Flag that represents if requested payout was paid out or not.
     */
    function requestPayout(uint256 _amount) external returns (bool);

    /**
     * @notice Returns the amount of tokens that can be claimed by the user.
     * @param _address the adress of the user for who the claimable vesting amount is to be calculated.
     */
    function getClaimableVestingAmount(
        address _address
    ) external view returns (uint256);

    ////////////////
    //   Setters  //
    ////////////////

    /**
     * @notice Set limit for withdrawals from discretionary pool.
     * @param _limit maximal number of tokens that can be withdrawn through limited withdrawals.
     * @dev requires caller to be owner of treasury, reverts with OwnableUnauthorizedAccount error otherwise.
     * @dev emits Treasury__WithdrawalLimitUpdated event.
     */
    function setWithdrawLimit(uint256 _limit) external;

    /**
     * @notice Set account address that can withdraw from discretionary pool up to defined limit.
     * @param _account account address that can withdraw from discretionary pool up to defined limit.
     * @dev requires caller to be owner of treasury, reverts with OwnableUnauthorizedAccount error otherwise.
     * @dev requires account to be non zero address, reverts with Treasury__InvalidAddress error otherwise.
     * @dev requires account not to be owner, reverts with Treasury__WithdrawalAccountCanNotBeOwner error otherwise.
     * @dev requires account not to be special account, reverts with Treasury__WithdrawalAccountCanNotBeSpecialAccount error otherwise.
     * @dev emits Treasury__WithdrawalAccountUpdated event.
     */
    function setWithdrawalAccount(address _account) external;

    /**
     * @notice Set account address that can withdraw from discretionary pool over defined limit.
     * @param _account account address that can withdraw from discretionary pool over defined limit.
     * @dev requires caller to be owner of treasury, reverts with OwnableUnauthorizedAccount error otherwise.
     * @dev requires account to be non zero address, reverts with Treasury__InvalidAddress error otherwise.
     * @dev requires account not to be owner, reverts with Treasury__SpecialAccountCanNotBeOwner error otherwise.
     * @dev requires account not to be regular account, reverts with Treasury__WithdrawalAccountCanNotBeSpecialAccount error otherwise.
     * @dev emits Treasury__SpecialAccountUpdated event.
     */
    function setSpecialAccount(address _account) external;
}

File 29 of 32 : ZNDBase.sol
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IZNDBase} from "./IZNDBase.sol";
import {ZNDToken} from "./ZNDToken.sol";

/**
* @notice Base contract that sets up common state variables and modifiers.
* @notice Deploys ZND token upon deployment and becomes owner of total token supply.
*/
contract ZNDBase is IZNDBase, Ownable2Step {
    ////////////////////////////
    //    State Variables     //
    ////////////////////////////

    /// @dev Token used for vesting, staking, rewards and fees
    ZNDToken public immutable s_zndToken;

    /// @dev ZND platform account that will have elevated privileges
    address public immutable s_zndPlatform;

    ///////////////////////////////
    //         Modifiers         //
    ///////////////////////////////

    modifier onlyCentralPlatform() {
        if (msg.sender != s_zndPlatform) revert Znd_NotZNDPlatform();

        _;
    }

    modifier onlyEOA() {
        if (msg.sender == s_zndPlatform)
            revert Znd_NotEOA();

        _;
    }

    ///////////////////////////////
    //         Constructor       //
    ///////////////////////////////

    constructor(address _zndPlatform) Ownable(msg.sender) {
        if (_zndPlatform == address(0)) revert Znd_ZeroAddress();

        s_zndPlatform = _zndPlatform;

        s_zndToken = new ZNDToken();
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;

import {IZNDParameters} from "./IZNDParameters.sol";
import {ZNDBase} from "./ZNDBase.sol";

/**
 * @notice Implements staking parameters change functionality for payout boosts and penalty fees.
 * @notice Parameters can be changed only within the predefined limits set up in constructor.
 * @notice Parameter changes are reflected after grace period pass and can not be changed again until cool down period is over.
 */
contract ZNDParameters is ZNDBase, IZNDParameters {
    ////////////////////////////
    //    State Variables     //
    ////////////////////////////

    /// @dev Plan => duration in days
    mapping(uint256 => uint256) public s_planDuration;

    // Reward boost mappings
    /// @dev Tier => Reward boost
    mapping(uint256 => uint256) public s_tierBoost;

    /// @dev Plan => Reward boost
    mapping(uint256 => uint256) public s_planBoost;

    /// @dev Plan => Penalty fee in bps
    mapping(uint256 => uint256) public s_penaltyFee;

    // Parameter change limitations

    uint256 public immutable s_tierBoostLowerLimit;
    uint256 public immutable s_tierBoostUpperLimit;
    uint256 public immutable s_tierBoostStepLimit;

    uint256 public immutable s_planBoostLowerLimit;
    uint256 public immutable s_planBoostUpperLimit;
    uint256 public immutable s_planBoostStepLimit;

    uint256 public immutable s_penaltyFeeLowerLimit;
    mapping(uint256 => uint256) public s_penaltyFeeUpperLimit;
    uint256 public immutable s_penaltyFeeStepLimit;

    uint256 public immutable s_parameterChangeGrace;
    uint256 public immutable s_parameterChangeCooldown;

    // Parameter change state variables

    // These are all circular buffers, they will be used for storing pending changes to avoid unnecessary removal and
    // addition of elements to a plain array while preserving the order of pending changes for the purpose of simplifying
    // checking for ones that passed their grace period
    // Just in case, explanation of how it works will be provided as they are used

    TierBoostChange[6] internal s_tierBoostChanges;
    PlanBoostChange[4] internal s_planBoostChanges;
    PenaltyChange[4] internal s_penaltyChanges;

    // Start and end indexes for the circular buffers

    uint256 internal s_tbcIdxStart;
    uint256 internal s_tbcIdxEnd;

    uint256 internal s_pbcIdxStart;
    uint256 internal s_pbcIdxEnd;

    uint256 internal s_pcIdxStart;
    uint256 internal s_pcIdxEnd;

    // Timestamps for the last change of each parameter

    mapping(uint256 => uint256) internal s_lastTierBoostChange;

    mapping(uint256 => uint256) internal s_lastPlanBoostChange;

    mapping(uint256 => uint256) internal s_lastPenaltyChange;

    constructor(address _zndPlatform) ZNDBase(_zndPlatform) {
        // Initialize reward boost as scaled integer values for each tier
        s_tierBoost[uint256(Tier.Basic)] = 100; // 1x
        s_tierBoost[uint256(Tier.Silver)] = 200; // 2x
        s_tierBoost[uint256(Tier.Gold)] = 220; // 2.2x
        s_tierBoost[uint256(Tier.Platinum)] = 240; // 2.4x
        s_tierBoost[uint256(Tier.Diamond)] = 260; // 2.6x
        s_tierBoost[uint256(Tier.Ambassador)] = 300; // 3x

        // Initialize reward boost bps for each plan
        s_planBoost[uint256(Plan.ThirtyDays)] = 100; // 1x
        s_planBoost[uint256(Plan.NinetyDays)] = 150; // 1.5x
        s_planBoost[uint256(Plan.HundredEightyDays)] = 200; // 2x
        s_planBoost[uint256(Plan.ThreeHundredSixtyDays)] = 300; // 3x

        // Initialize plan duration in days
        s_planDuration[uint256(Plan.ThirtyDays)] = 30;
        s_planDuration[uint256(Plan.NinetyDays)] = 90;
        s_planDuration[uint256(Plan.HundredEightyDays)] = 180;
        s_planDuration[uint256(Plan.ThreeHundredSixtyDays)] = 360;

        // Initialize penalties bps for each plan
        s_penaltyFee[uint256(Plan.ThirtyDays)] = 100; // 1%
        s_penaltyFee[uint256(Plan.NinetyDays)] = 300; // 3%
        s_penaltyFee[uint256(Plan.HundredEightyDays)] = 600; // 6%
        s_penaltyFee[uint256(Plan.ThreeHundredSixtyDays)] = 1200; // 12%

        // Initialise all parameter change limits

        s_tierBoostLowerLimit = 100; // 1x
        s_tierBoostUpperLimit = 1000; // 10x
        s_tierBoostStepLimit = 1; // 0.01

        s_planBoostLowerLimit = 100; // 1x
        s_planBoostUpperLimit = 1000; // 10x
        s_planBoostStepLimit = 1; // 0.01

        s_penaltyFeeLowerLimit = 0; // 0%
        s_penaltyFeeStepLimit = 1; // 0.01%

        s_penaltyFeeUpperLimit[uint256(Plan.ThirtyDays)] = 500; // 5%
        s_penaltyFeeUpperLimit[uint256(Plan.NinetyDays)] = 1000; // 10%
        s_penaltyFeeUpperLimit[uint256(Plan.HundredEightyDays)] = 1500; // 15%
        s_penaltyFeeUpperLimit[uint256(Plan.ThreeHundredSixtyDays)] = 2500; // 25%

        s_parameterChangeGrace = 1;
        s_parameterChangeCooldown = 3;

        // Set initial change of parameters to contract creation

        s_lastTierBoostChange[uint256(Tier.Basic)] = block.timestamp;
        s_lastTierBoostChange[uint256(Tier.Silver)] = block.timestamp;
        s_lastTierBoostChange[uint256(Tier.Gold)] = block.timestamp;
        s_lastTierBoostChange[uint256(Tier.Platinum)] = block.timestamp;
        s_lastTierBoostChange[uint256(Tier.Diamond)] = block.timestamp;
        s_lastTierBoostChange[uint256(Tier.Ambassador)] = block.timestamp;

        s_lastPlanBoostChange[uint256(Plan.ThirtyDays)] = block.timestamp;
        s_lastPlanBoostChange[uint256(Plan.NinetyDays)] = block.timestamp;
        s_lastPlanBoostChange[uint256(Plan.HundredEightyDays)] = block
            .timestamp;
        s_lastPlanBoostChange[uint256(Plan.ThreeHundredSixtyDays)] = block
            .timestamp;

        s_lastPenaltyChange[uint256(Plan.ThirtyDays)] = block.timestamp;
        s_lastPenaltyChange[uint256(Plan.NinetyDays)] = block.timestamp;
        s_lastPenaltyChange[uint256(Plan.HundredEightyDays)] = block.timestamp;
        s_lastPenaltyChange[uint256(Plan.ThreeHundredSixtyDays)] = block
            .timestamp;

        // Set indexes for the circular buffer

        s_tbcIdxStart = 0;
        s_tbcIdxEnd = 0;

        s_pbcIdxStart = 0;
        s_pbcIdxEnd = 0;

        s_pcIdxStart = 0;
        s_pcIdxEnd = 0;
    }

    ///////////////////////////////////
    //      External Functions       //
    ///////////////////////////////////

    function changeTierBoost(
        uint256 _tier,
        uint256 _newValue
    ) external onlyCentralPlatform {
        // We check if the tier is valid
        if (_tier >= 6) {
            revert Parameters_PlanOrTierNotValid();
        }

        // We check if the cooldown period for the change has expired
        if (
            block.timestamp - s_lastTierBoostChange[_tier] <
            30 days * s_parameterChangeCooldown
        ) revert Parameters_CooldownNotOver();

        _checkPendingBoostChanges();

        // Find the absolute value of the change difference
        uint256 valDif = _newValue > s_tierBoost[_tier]
            ? _newValue - s_tierBoost[_tier]
            : s_tierBoost[_tier] - _newValue;

        // Check if the change is within limitations
        if (
            _newValue > s_tierBoostUpperLimit ||
            _newValue < s_tierBoostLowerLimit ||
            valDif > s_tierBoostStepLimit
        ) revert Parameters_ChangeNotWithinBounds();

        // Save the timestamp of the current change as latest
        s_lastTierBoostChange[_tier] = block.timestamp;

        // Add the current change to the pending changes buffer
        TierBoostChange memory newChange = TierBoostChange({
            tier: _tier,
            newValue: _newValue,
            initiated: block.timestamp
        });

        s_tierBoostChanges[s_tbcIdxEnd] = newChange;
        s_tbcIdxEnd = (s_tbcIdxEnd + 1) % 6; // As we are using a circular buffer, the indexing of the array "wraps around"
        // back to the beginning of the array after adding to the end of the buffer

        emit ParameterChangeRequested(
            "TierBoost",
            _tier,
            s_tierBoost[_tier],
            _newValue
        );
    }

    function changePlanBoost(
        uint256 _plan,
        uint256 _newValue
    ) external onlyCentralPlatform {
        // Check if tier is valid
        if (_plan >= 4) {
            revert Parameters_PlanOrTierNotValid();
        }

        // We check if the cooldown period for the change has expired
        if (
            block.timestamp - s_lastPlanBoostChange[_plan] <
            s_parameterChangeCooldown * 30 days
        ) revert Parameters_CooldownNotOver();

        _checkPendingBoostChanges();

        // Find the absolute value of the change difference
        uint256 valDif = _newValue > s_planBoost[_plan]
            ? _newValue - s_planBoost[_plan]
            : s_planBoost[_plan] - _newValue;

        // Check if the change is within limitations
        if (
            _newValue > s_planBoostUpperLimit ||
            _newValue < s_planBoostLowerLimit ||
            valDif > s_planBoostStepLimit
        ) revert Parameters_ChangeNotWithinBounds();

        // Save the timestamp of the current change as latest
        s_lastPlanBoostChange[_plan] = block.timestamp;

        // Add the current change to the pending changes buffer
        PlanBoostChange memory newChange = PlanBoostChange({
            plan: _plan,
            newValue: _newValue,
            initiated: block.timestamp
        });

        s_planBoostChanges[s_pbcIdxEnd] = newChange;
        s_pbcIdxEnd = (s_pbcIdxEnd + 1) % 4; // As we are using a circular buffer, the indexing of the array "wraps around"
        // back to the beginning of the array after adding to the end of the buffer

        emit ParameterChangeRequested(
            "PlanBoost",
            _plan,
            s_planBoost[_plan],
            _newValue
        );
    }

    function changePenaltyFee(
        uint256 _plan,
        uint256 _newValue
    ) external onlyCentralPlatform {
        // Check if plan is valid
        if (_plan >= 4) {
            revert Parameters_PlanOrTierNotValid();
        }

        // We check if the cooldown period for the change has expired
        if (
            block.timestamp - s_lastPenaltyChange[_plan] <
            30 days * s_parameterChangeCooldown
        ) revert Parameters_CooldownNotOver();

        _checkPendingPenaltyChanges();

        // Find the absolute value of the change difference
        uint256 valDif = _newValue > s_penaltyFee[_plan]
            ? _newValue - s_penaltyFee[_plan]
            : s_penaltyFee[_plan] - _newValue;

        // Check if the change is within limitations
        if (
            _newValue > s_penaltyFeeUpperLimit[_plan] ||
            _newValue < s_penaltyFeeLowerLimit ||
            valDif > s_penaltyFeeStepLimit
        ) revert Parameters_ChangeNotWithinBounds();

        // Save the timestamp of the current change as latest
        s_lastPenaltyChange[_plan] = block.timestamp;

        // Add the current change to the pending changes buffer
        PenaltyChange memory newChange = PenaltyChange({
            plan: _plan,
            newValue: _newValue,
            initiated: block.timestamp
        });

        s_penaltyChanges[s_pcIdxEnd] = newChange;
        s_pcIdxEnd = (s_pcIdxEnd + 1) % 4; // As we are using a circular buffer, the indexing of the array "wraps around"
        // back to the beginning of the array after adding to the end of the buffers

        emit ParameterChangeRequested(
            "PenaltyFee",
            _plan,
            s_penaltyFee[_plan],
            _newValue
        );
    }

    ///////////////////////////////////
    //      Internal Functions       //
    ///////////////////////////////////

    // Function for checking if any pending boost changes have passed their grace period
    function _checkPendingBoostChanges() internal {
        // First check for tier boost changes
        for (uint256 i = s_tbcIdxStart; i != s_tbcIdxEnd; i = (i + 1) % 6) {
            // For loop goes around the circular buffer
            if (
                block.timestamp - s_tierBoostChanges[i].initiated >
                s_parameterChangeGrace * 30 days
            ) {
                s_tierBoost[s_tierBoostChanges[i].tier] = s_tierBoostChanges[i]
                    .newValue;
                s_tbcIdxStart = (s_tbcIdxStart + 1) % 6; // When a pending change has expired, it is deleted by simply moving
                // the start index (which, again, wraps around) past it
            } else {
                break; // The order of addintion to the buffer is temporal, so if we come across a pending change
                // not past the grace period, we know the ones after it are also not past it
            }
        }

        // Then check for plan boost changes
        for (uint256 i = s_pbcIdxStart; i != s_pbcIdxEnd; i = (i + 1) % 4) {
            // For loop goes around the circular buffer
            if (
                block.timestamp - s_planBoostChanges[i].initiated >
                s_parameterChangeGrace * 30 days
            ) {
                s_planBoost[s_planBoostChanges[i].plan] = s_planBoostChanges[i]
                    .newValue;
                s_pbcIdxStart = (s_pbcIdxStart + 1) % 4; // When a pending change has expired, it is deleted by simply moving
                // the start index (which, again, wraps around) past it
            } else {
                break; // The order of addintion to the buffer is temporal, so if we come across a pending change
            } // not past the grace period, we know the ones after it are also not past it
        }
    }

    // Function for checking if any pending penalty fee changes passed their grace period
    function _checkPendingPenaltyChanges() internal {
        for (uint256 i = s_pcIdxStart; i != s_pcIdxEnd; i = (i + 1) % 4) {
            // For loop goes around the circular buffer
            if (
                block.timestamp - s_penaltyChanges[i].initiated >
                s_parameterChangeGrace * 30 days
            ) {
                s_penaltyFee[s_penaltyChanges[i].plan] = s_penaltyChanges[i]
                    .newValue;
                s_pcIdxStart = (s_pcIdxStart + 1) % 4; // When a pending change has expired, it is deleted by simply moving
                // the start index (which, again, wraps around) past it
            } else {
                break; // The order of addintion to the buffer is temporal, so if we come across a pending change
            } // not past the grace period, we know the ones after it are also not past it
        }
    }
}

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

/**
* @notice ERC20 token that implements EIP-2612 permit functionality.
* @notice Total supply of tokens is minted upon deployment and none of the tokens can be minted ever again.
* @notice Token owners can burn their tokens.
*/
contract ZNDToken is ERC20, Ownable2Step, ERC20Permit, ERC20Burnable {
    error RenouncingOwnershipIsDisabled();

    /**
     * @dev Initial and final supply of tokens. Tokens can not be minted after this contract is deployed.
     */
    uint256 constant public TOTAL_SUPPLY = 700_000_000;

    constructor() ERC20("ZNDToken", "ZND") ERC20Permit("ZNDToken") Ownable(msg.sender) {
        _mint(msg.sender, TOTAL_SUPPLY * 10 ** decimals());
    }

    /**
     * @notice Always reverts in order to prevent losing ownership.
     */
    function renounceOwnership() public view override onlyOwner {
        revert RenouncingOwnershipIsDisabled();
    }
}

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

import {ZNDParameters} from "./ZNDParameters.sol";
import {IZNDTreasury} from "./IZNDTreasury.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
 * @notice Implements treasury related functionality.
 * @notice Enables funding of rewards and discretionary pools.
 * @notice Enables withdrawing from discretionary and fees pools.
 * @notice Implements vesting schedule and controls payouts to token recipients.
 */
contract ZNDTreasury is ZNDParameters, IZNDTreasury {
    using SafeERC20 for IERC20;

    using EnumerableSet for EnumerableSet.AddressSet;

    ///////////////////////////////////
    //     Treasury related accounts //
    ///////////////////////////////////

    // Discretionary payments withdrawal account
    address public s_withdrawalAccount;

    // Discretionary over-limit withdrawal account
    address public s_specialAccount;

    ///////////////////////////////////
    //     Treasury pool balances    //
    ///////////////////////////////////

    // Amount of ZND tokens in the rewards pool
    uint256 public s_rewardsPoolBalance;

    // Amount of ZND tokens in the fees pool
    uint256 public s_feesPoolBalance;

    // Amount of ZND tokens in the discretionary pool
    uint256 public s_discretionaryPoolBalance;

    // Struct which keeps track of vesting policy
    mapping(address => VestingData) private s_recipients;

    ///////////////////////////////////
    //     Treasury parameters       //
    ///////////////////////////////////

    /// @dev withdrawal limit for discretionary and fees pools
    uint256 public s_withdrawalLimit;

    ///////////////////////////////
    //         Modifiers         //
    ///////////////////////////////

    modifier onlyTreasuryAccount() {
        if (
            msg.sender != s_withdrawalAccount &&
            msg.sender != s_specialAccount &&
            msg.sender != owner()
        ) revert Treasury__NotTreasuryAccount();

        _;
    }

    ///////////////////////////////
    //         Constructor       //
    ///////////////////////////////

    /// @notice this will deploy the vesting table.
    constructor(
        address _zndPlatform,
        address _withdrawalAccount,
        address _specialAccount,
        uint256 _initialDiscretionaryPoolBalance,
        uint256 _initialRewardsPoolBalance,
        VestingPayload[] memory _vestingPayload
    ) ZNDParameters(_zndPlatform) {
        if (_withdrawalAccount == address(0)) revert Treasury__InvalidAddress();

        if (_specialAccount == address(0)) revert Treasury__InvalidAddress();

        if (_withdrawalAccount == owner())
            revert Treasury__WithdrawalAccountCanNotBeOwner();

        if (_withdrawalAccount == _specialAccount)
            revert Treasury__WithdrawalAccountCanNotBeSpecialAccount();

        if (_specialAccount == owner())
            revert Treasury__SpecialAccountCanNotBeOwner();

        s_withdrawalAccount = _withdrawalAccount;

        s_specialAccount = _specialAccount;

        s_discretionaryPoolBalance = _initialDiscretionaryPoolBalance;

        s_rewardsPoolBalance = _initialRewardsPoolBalance;

        // Vesting Policy Deployment

        for (uint256 i = 0; i < _vestingPayload.length; i++) {
            // get the vesting recipient
            address recipient = _vestingPayload[i].recipient;

            // cannot be special account
            if (recipient == _specialAccount)
                revert Treasury__SpecialAccountCanNotBeVestingRecipient();

            // also cannot be owner
            if (recipient == owner())
                revert Treasury__OwnerAccountCanNotBeVestingRecipient();

            // making sure the address is not duplicate,
            // if there was no initial unlock, and therefore `availableTokens`
            // are 0, there will be some non-0 number of unlocks left
            // on the other hand, if all the funds are meant to be available
            // immmediately, `availableTokens` will be non-0
            if (
                s_recipients[recipient].availableTokens > 0 ||
                s_recipients[recipient].numberOfUnlocksLeft > 0
            ) revert Treasury__DuplicateVestingAddress();

            uint256 amountToReceivePerUnlock = 0;

            if (_vestingPayload[i].numberOfDays > 0) {
                // making sure that quantity is divisible by number of days
                if (
                    _vestingPayload[i].quantityToUnlockOverTime %
                        _vestingPayload[i].numberOfDays >
                    0
                )
                    revert Treasury__QuantityUnlockOverTimeNotDivisibleByNumberOfDays();

                amountToReceivePerUnlock =
                    _vestingPayload[i].quantityToUnlockOverTime /
                    _vestingPayload[i].numberOfDays;
            }

            s_recipients[recipient] = VestingData({
                availableTokens: _vestingPayload[i].initialUnlock,
                numberOfUnlocksLeft: _vestingPayload[i].numberOfDays,
                amountToReceivePerUnlock: amountToReceivePerUnlock,
                nextUnlockTimestamp: block.timestamp +
                    _vestingPayload[i].cliffPeriodEnd
            });
        }
    }

    ///////////////////////////////////
    //      External Functions       //
    ///////////////////////////////////

    /// @inheritdoc IZNDTreasury
    function fundRewardsPool(uint256 _amount) external {
        s_rewardsPoolBalance += _amount;

        emit Treasury__Funding(msg.sender, _amount, TreasuryPool.RewardsPool);

        IERC20(s_zndToken).safeTransferFrom(msg.sender, address(this), _amount);
    }

    /// @inheritdoc IZNDTreasury
    function fundDiscretionaryPool(uint256 _amount) external {
        s_discretionaryPoolBalance += _amount;

        emit Treasury__Funding(
            msg.sender,
            _amount,
            TreasuryPool.DiscretionaryPool
        );

        IERC20(s_zndToken).safeTransferFrom(msg.sender, address(this), _amount);
    }

    /// @inheritdoc IZNDTreasury
    function withdrawFromDiscretionaryPool(
        address _to,
        uint256 _amount
    ) external onlyTreasuryAccount {
        _checkWithdrawalValidity(_amount);
        // amount is less than or equal limit, or the sender is owner or special account, execute payment

        // check available funds
        if (_amount > s_discretionaryPoolBalance)
            revert Treasury__InsufficientFunds();

        s_discretionaryPoolBalance -= _amount;

        emit Treasury__Withdrawal(
            msg.sender,
            _to,
            _amount,
            TreasuryPool.DiscretionaryPool
        );

        IERC20(s_zndToken).safeTransfer(_to, _amount);
    }

    /// @inheritdoc IZNDTreasury
    function withdrawFromFeesPool(
        address _to,
        uint256 _amount
    ) external onlyTreasuryAccount {
        _checkWithdrawalValidity(_amount);
        // amount is less than or equal limit, or the sender is owner or special account, execute payment

        // check available funds
        if (_amount > s_feesPoolBalance) revert Treasury__InsufficientFunds();

        s_feesPoolBalance -= _amount;

        emit Treasury__Withdrawal(
            msg.sender,
            _to,
            _amount,
            TreasuryPool.FeesPool
        );

        IERC20(s_zndToken).safeTransfer(_to, _amount);
    }

    /// @inheritdoc IZNDTreasury
    function setWithdrawLimit(uint256 _limit) external onlyOwner {
        s_withdrawalLimit = _limit;

        emit Treasury__WithdrawalLimitUpdated(msg.sender, _limit);
    }

    /// @inheritdoc IZNDTreasury
    function setWithdrawalAccount(address _account) external onlyOwner {
        if (_account == address(0)) revert Treasury__InvalidAddress();

        if (_account == owner())
            revert Treasury__WithdrawalAccountCanNotBeOwner();

        if (_account == s_specialAccount)
            revert Treasury__WithdrawalAccountCanNotBeSpecialAccount();

        s_withdrawalAccount = _account;

        emit Treasury__WithdrawalAccountUpdated(msg.sender, _account);
    }

    /// @inheritdoc IZNDTreasury
    function setSpecialAccount(address _account) external onlyOwner {
        if (_account == address(0)) revert Treasury__InvalidAddress();

        if (_account == owner()) revert Treasury__SpecialAccountCanNotBeOwner();

        if (_account == s_withdrawalAccount)
            revert Treasury__WithdrawalAccountCanNotBeSpecialAccount();

        s_specialAccount = _account;

        emit Treasury__SpecialAccountUpdated(msg.sender, _account);
    }

    /**
     * @notice Changes the owner of the contract.
     * @notice owner can not withdraw from vesting pool.
     * @notice owner can update parameters of the treasury.
     * @param _account address of the new owner.
     * @dev requires caller to be owner, reverts with OwnableUnauthorizedAccount error otherwise.
     * @dev requires account to be non zero address, reverts with Treasury__InvalidAddress error otherwise.
     * @dev requires account not to be special account, reverts with Treasury__SpecialAccountCanNotBeOwner error otherwise.
     * @dev requires account not to be regular account, reverts with Treasury__WithdrawalAccountCanNotBeOwner error otherwise.
     * @dev emits Treasury__OwnerUpdated event.
     */
    function transferOwnership(address _account) public override onlyOwner {
        if (_account == address(0)) revert Treasury__InvalidAddress();

        if (_account == s_specialAccount)
            revert Treasury__SpecialAccountCanNotBeOwner();

        if (_account == s_withdrawalAccount)
            revert Treasury__WithdrawalAccountCanNotBeOwner();

        super.transferOwnership(_account);
    }

    /**
     * @notice Always reverts in order to prevent losing ownership.
     */
    function renounceOwnership() public view override onlyOwner {
        revert Treasury__RenouncingOwnershipIsDisabled();
    }

    /// @inheritdoc IZNDTreasury
    function requestPayout(uint256 _amount) external returns (bool) {
        // Update recipients available tokens based on vesting policy for recipient
        VestingData memory recipientVestingPolicy = s_recipients[msg.sender];

        uint256 unlockCount = 0;
        uint256 lastUnlockTimestamp = recipientVestingPolicy
            .nextUnlockTimestamp;

        // if enough time has passed get the number of unlocks
        if (block.timestamp > lastUnlockTimestamp) {
            // since division floors, `+1` is necessary to get the corret unlockCount
            // e.g.: if one more hour than it's necessary for the next unlock
            // to occur has passed, that would give the `block.timestamp - lastUnlockTimestamp`
            // of 3600, and divided by 1 days, would be 0, even though enough time has passed
            unlockCount = (block.timestamp - lastUnlockTimestamp) / 1 days + 1;
        }

        // if there are unlocks, update the vesting data
        if (unlockCount > 0) {
            unlockCount = _min(
                unlockCount,
                recipientVestingPolicy.numberOfUnlocksLeft
            );

            // update the last unlocked timestamp
            s_recipients[msg.sender].nextUnlockTimestamp += (1 days *
                unlockCount);

            // update the number of unlocks left
            s_recipients[msg.sender].numberOfUnlocksLeft -= unlockCount;

            // update the available tokens
            s_recipients[msg.sender].availableTokens +=
                unlockCount *
                recipientVestingPolicy.amountToReceivePerUnlock;
        }

        // Transfer the requested amount to the recipient if the sufficient funds are available
        // if there are insufficient funds, stop the transaction with success flag set to false
        if (s_recipients[msg.sender].availableTokens < _amount) return false;

        s_recipients[msg.sender].availableTokens -= _amount;

        emit Treasury__VestingPayoutCompleted(msg.sender, _amount);

        // transfer the tokens to the recipient
        IERC20(s_zndToken).safeTransfer(msg.sender, _amount);

        // flag payout as a successful one
        return true;
    }

    /// @inheritdoc IZNDTreasury
    function getClaimableVestingAmount(
        address _address
    ) external view returns (uint256) {
        VestingData memory recipientVestingPolicy = s_recipients[_address];

        uint256 claimableTokenAmount = recipientVestingPolicy.availableTokens;
        uint256 unlockCount = 0;
        uint256 lastUnlockTimestamp = recipientVestingPolicy
            .nextUnlockTimestamp;

        // if enough time has passed get the number of unlocks
        if (block.timestamp > lastUnlockTimestamp) {
            // since division floors, `+1` is necessary to get the corret unlockCount
            // e.g.: if one more hour than it's necessary for the next unlock
            // to occur has passed, that would give the `block.timestamp - lastUnlockTimestamp`
            // of 3600, and divided by 1 days, would be 0, even though enough time has passed
            unlockCount = (block.timestamp - lastUnlockTimestamp) / 1 days + 1;
        }

        // if there are unlocks, add them to the claimable amount
        if (unlockCount > 0) {
            unlockCount = _min(
                unlockCount,
                recipientVestingPolicy.numberOfUnlocksLeft
            );

            claimableTokenAmount +=
                unlockCount *
                recipientVestingPolicy.amountToReceivePerUnlock;
        }

        return claimableTokenAmount;
    }

    ///////////////////////////////////////////
    //           Internal Functions          //
    ///////////////////////////////////////////

    function _checkWithdrawalValidity(uint256 _amount) internal view {
        // amount must be greater than 0
        if (_amount == 0) revert Treasury__AmountNotSpecified();

        // if amount is over withdrawal limit
        if (_amount > s_withdrawalLimit) {
            // check if caller is neither owner nor special account
            if (msg.sender != owner() && msg.sender != s_specialAccount) {
                // revert
                revert Treasury__OverLimitWithdrawalNotAllowed();
            }
        }
    }

    function _min(
        uint256 _first,
        uint256 _second
    ) internal pure returns (uint256) {
        return _first < _second ? _first : _second;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 10000
  },
  "viaIR": true,
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_zndPlatform","type":"address"},{"internalType":"address","name":"_discretionaryWithdrawalAccount","type":"address"},{"internalType":"address","name":"_overLimitWithdrawalAccount","type":"address"},{"internalType":"uint256","name":"_initialDailyReward","type":"uint256"},{"internalType":"uint256","name":"_initialDiscretionaryPoolBalance","type":"uint256"},{"internalType":"uint256","name":"_initialRewardsPoolBalance","type":"uint256"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"quantityToUnlockOverTime","type":"uint256"},{"internalType":"uint256","name":"numberOfDays","type":"uint256"},{"internalType":"uint256","name":"cliffPeriodEnd","type":"uint256"},{"internalType":"uint256","name":"initialUnlock","type":"uint256"}],"internalType":"struct IZNDTreasury.VestingPayload[]","name":"_vestingPayload","type":"tuple[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"Parameters_ChangeNotWithinBounds","type":"error"},{"inputs":[],"name":"Parameters_CooldownNotOver","type":"error"},{"inputs":[],"name":"Parameters_PlanOrTierNotValid","type":"error"},{"inputs":[],"name":"Parameters_PoolsAreNotEmpty","type":"error"},{"inputs":[],"name":"Reward_ComputationAlreadyStarted","type":"error"},{"inputs":[],"name":"Reward_InsufficientFundsInPool","type":"error"},{"inputs":[],"name":"Reward_NotAStakeholder","type":"error"},{"inputs":[],"name":"Reward_WithdrawInsufficientFunds","type":"error"},{"inputs":[],"name":"Reward_ZeroAmountWithdrawalNotAllowed","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"Stake_AddressNotFound","type":"error"},{"inputs":[],"name":"Stake_ComprehensiveRewardDistributionOngoing","type":"error"},{"inputs":[],"name":"Stake_DoesNotExist","type":"error"},{"inputs":[],"name":"Stake_NoStakesProvided","type":"error"},{"inputs":[],"name":"Stake_NoWithdrawalsProvided","type":"error"},{"inputs":[],"name":"Stake_NotOwner","type":"error"},{"inputs":[],"name":"Stake_WithdrawInsufficientFunds","type":"error"},{"inputs":[],"name":"Stake_ZeroAmountStakeNotAllowed","type":"error"},{"inputs":[],"name":"Stake_ZeroAmountWithdrawalNotAllowed","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"inputs":[],"name":"Treasury__AmountNotSpecified","type":"error"},{"inputs":[],"name":"Treasury__DuplicateVestingAddress","type":"error"},{"inputs":[],"name":"Treasury__InsufficientFunds","type":"error"},{"inputs":[],"name":"Treasury__InvalidAddress","type":"error"},{"inputs":[],"name":"Treasury__NotTreasuryAccount","type":"error"},{"inputs":[],"name":"Treasury__OverLimitWithdrawalNotAllowed","type":"error"},{"inputs":[],"name":"Treasury__OwnerAccountCanNotBeVestingRecipient","type":"error"},{"inputs":[],"name":"Treasury__QuantityUnlockOverTimeNotDivisibleByNumberOfDays","type":"error"},{"inputs":[],"name":"Treasury__RenouncingOwnershipIsDisabled","type":"error"},{"inputs":[],"name":"Treasury__SpecialAccountCanNotBeOwner","type":"error"},{"inputs":[],"name":"Treasury__SpecialAccountCanNotBeVestingRecipient","type":"error"},{"inputs":[],"name":"Treasury__WithdrawalAccountCanNotBeOwner","type":"error"},{"inputs":[],"name":"Treasury__WithdrawalAccountCanNotBeSpecialAccount","type":"error"},{"inputs":[],"name":"Znd_InvalidSigner","type":"error"},{"inputs":[],"name":"Znd_NotEOA","type":"error"},{"inputs":[],"name":"Znd_NotZNDPlatform","type":"error"},{"inputs":[],"name":"Znd_ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newSize","type":"uint256"}],"name":"BatchSizeChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"ComputedDailyRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DailyRewardChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"DailyRewardOffsetChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"}],"name":"DidNotDistributeAllRewardsToSelf","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"limit","type":"uint256"}],"name":"DiscretionaryPoolWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"}],"name":"DistributedDailyReward","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[],"name":"FinishedComprehensiveRewardDistribution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"changeType","type":"string"},{"indexed":false,"internalType":"uint256","name":"planOrTier","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ParameterChangeRequested","type":"event"},{"anonymous":false,"inputs":[],"name":"RewardComputationStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"stakeIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"numberOfStakes","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"}],"name":"StakedCentral","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"stakeId","type":"uint256"},{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"enum IZNDBase.Plan","name":"plan","type":"uint8"}],"name":"StakedEOA","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"funder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"enum IZNDTreasury.TreasuryPool","name":"pool","type":"uint8"}],"name":"Treasury__Funding","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"updater","type":"address"},{"indexed":true,"internalType":"address","name":"newAccount","type":"address"}],"name":"Treasury__SpecialAccountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Treasury__VestingPayoutCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"withdrawAccount","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawTo","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"enum IZNDTreasury.TreasuryPool","name":"pool","type":"uint8"}],"name":"Treasury__Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"updater","type":"address"},{"indexed":true,"internalType":"address","name":"newAccount","type":"address"}],"name":"Treasury__WithdrawalAccountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"updater","type":"address"},{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"Treasury__WithdrawalLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawRewardsCentral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWithdrawn","type":"uint256"}],"name":"WithdrawRewardsEOA","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeId","type":"uint256"},{"indexed":true,"internalType":"address","name":"stakeholder","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountWithdrawn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penatlyFee","type":"uint256"}],"name":"WithdrawStakeEOA","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"stakeIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"numberOfWithdrawals","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalPenalites","type":"uint256"}],"name":"WithdrawStakesCentral","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areAllRewardsDistributed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_newValue","type":"uint256"}],"name":"changePenaltyFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_newValue","type":"uint256"}],"name":"changePlanBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tier","type":"uint256"},{"internalType":"uint256","name":"_newValue","type":"uint256"}],"name":"changeTierBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"distributeRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_tier","type":"uint256"},{"internalType":"uint256","name":"_noLogs","type":"uint256"}],"name":"distributeRewardsSpecific","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"fundDiscretionaryPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"fundRewardsPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum IZNDBase.Tier","name":"_tier","type":"uint8"},{"internalType":"enum IZNDBase.Plan","name":"_plan","type":"uint8"}],"name":"getAmountStakedInPool","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"getClaimableVestingAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"getRewardAmountOfAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeId","type":"uint256"}],"name":"getStake","outputs":[{"components":[{"internalType":"uint256","name":"stakeId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"stakedAt","type":"uint256"},{"internalType":"address","name":"stakeholder","type":"address"},{"components":[{"internalType":"enum IZNDBase.Tier","name":"tier","type":"uint8"},{"internalType":"enum IZNDBase.Plan","name":"plan","type":"uint8"}],"internalType":"struct IZNDStaking.Pool","name":"pool","type":"tuple"}],"internalType":"struct IZNDStaking.Stake","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"requestPayout","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"s_dailyReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_discretionaryPoolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_feesPoolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_nextStakeId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_parameterChangeCooldown","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_parameterChangeGrace","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_penaltyFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_penaltyFeeLowerLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_penaltyFeeStepLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_penaltyFeeUpperLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_planBoost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_planBoostLowerLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_planBoostStepLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_planBoostUpperLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_planDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_rewardsPoolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_specialAccount","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"s_tierBoost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_tierBoostLowerLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_tierBoostStepLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_tierBoostUpperLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_withdrawalAccount","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_withdrawalLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_zndPlatform","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s_zndToken","outputs":[{"internalType":"contract ZNDToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"setDailyReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newOffset","type":"uint256"}],"name":"setResetPointOffset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"setSpecialAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"setWithdrawLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"setWithdrawalAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"tier","type":"uint256"},{"internalType":"uint256","name":"plan","type":"uint256"}],"internalType":"struct IZNDStaking.StakePayload[]","name":"_stakes","type":"tuple[]"}],"name":"stakeCentral","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_plan","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"stakeEOA","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startRewardComputation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawFromDiscretionaryPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawFromFeesPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawRewardsCentral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"withdrawRewardsEOA","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"withdrawStakeEOA","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"stakeId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct IZNDStaking.WithdrawCentralPayload[]","name":"_withdrawals","type":"tuple[]"}],"name":"withdrawStakesCentral","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]

6103408060405234610d8b576177b2803803809161001d8285610d90565b833981019060e081830312610d8b5761003581610db3565b9161004260208301610db3565b61004e60408401610db3565b9360608401519160808501519060a08601519560c08101519060018060401b038211610d8b570185601f82011215610d8b578051906001600160401b0382116108a257604051966100a560208460051b0189610d90565b828852602060a0818a01940283010191818311610d8b57602001925b828410610d215750505050604051956100db604088610d90565b600a87526020870190695a4e445374616b696e6760b01b825260405193610103604086610d90565b600185526020850193603160f81b85523315610d0b57600180546001600160a01b031990811690915560008054339281168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36001600160a01b03881615610cfa5760a0889052611c4d8181016001600160401b038111838210176108a2578291615b65833903906000f08015610cee5760805260647f3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff81905560c87fa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c81905560dc7fc3a24b0501bd2c13a7e57f2db4369ec4c223447539fc0724a9d55ac4a06ebd4d5560f07fcbc4e5fb02c3d1de23a9f1e014b4d2ee5aeaea9505df5e855c9210bf472495af556101047f83ec6a1f0257b830b5e016457c9cf1435391bf56cc98f369a58a54fe937724655561012c7f405aad32e1adbac89bb7f176e338b8fc6e994ca210c9bb7bdca249b4659422508190557f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec83905560967fabd6e7cb50984ff9c2f3e18a2660c3353dadf4e3291deeb275dae2cd1e44fe05557f91da3fd0782e51c6b3986e9e672fd566868e71f3dbc2d6c2cd6fbb3e361af2a7919091557f2e174c10e159ea99b867ce3205125c24a42d128804e4070ed6fcc8cc98166aa0819055601e7fac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b55605a7fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e05560b47f679795a0195a1b76cdebb7c51d74e058aee92919b8c3389af86ef24535e8a28c556101687f88601476d11616a71c5be67555bd1dff4b1cbf21533d2669b768b61518cfe1c3557f05b8ccbb9d4d8fb16ea74ce3c29a41f1b461fbdaff4714a0d9a8eb05499746bc8290557f1471eb6eb2c5e789fc3de43f8ce62938c7d1836ec861730447e2ada8fd81017b556102587f89832631fb3c3307a103ba2c84ab569c64d6182a18893dcd163f0f1c2090733a556104b07fa9bc9a3a348c357ba16b37005d7e6b3236198c0e939f4af8c5f19b8deeb8ebc05560c08190526103e860e081905260016101008190526101209290925261014081905261016082905260006101808190526101a08390526101f47f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8557f3e5fec24aa4dc4e5aee2e025e51e1392c72a2500577559fae9665c6d52bd6a31919091556105dc7f8819ef417987f8ae7a81f42cdfb18815282fe989326fbff903d13cf0e03ace29556109c47f75f96ab15d697e93042dc45b5c896c4b27e89bb6eaf39475c5c371cb2513f7d2556101c09190915260036101e0819052427fa0a618d80eda9243166be83cb7421d97e9dab6ddddd3c70ac7a6b4440256e8e78190557f8e0cc0f1f0504b4cb44a23b328568106915b169e79003737a7b094503cdbeeb08190557f67dcc86da9aaaf40a183002157e56801115aa6057705e43279b4c1c90942d6b28190557f81d0999fde243adcc41b7fa1be5cea14f789e3a6065b815ac58f4bc0838c31558190557f533efb5c9f032d0e72b35f5d59b231dc7a9fb94625f73b3c45c394126326354c8190557f50039cf134a124858bd88bbc9225ec3c537b89a0e9237ce39fe1813e6edf82578190557fe14cf4d84b2ff434db2c3d715ad03acb36d95ed6f766d46660154cee72012d718190557f33a28b70ecab075fc507d0cb5ffac06e3bc912aa6c671a1fa4ccb48318e19b118190557f44eb26011dd1cf82e0b45a2fb60b23f01ab68cabbe2fe1f3e7b70c0873d5dc5b8190557f76ace02705df1df747e114090468e0edd81a8d99edc641fff18613cd34df0a6e81905560396020527f619fcd35f40c77da8aa64d5f98c1fed669135ecc4f245ce6667721b157f7098f8190557f6f07f90659cdc781ea4d9fa69455ad8c2ec7b10a59bb4222a9a50afe92849fb08190557f14da11d3a545d46fb88e4cc5018cec76493b6788d69758d44d9e98f3fad639298190559082527f7a1d31c67145dccaf0cb9ca8d00d91c495aa0503124bb9c46a2ce884ec3cf3d755603181905560328190556033819055603481905560358190556036556001600160a01b0316998a15610cdd576001600160a01b0316998a15610cdd576000546001600160a01b0316818114610ccc578b8214610cbb578b14610caa57603a80546001600160a01b0319908116929092179055603b80549091168b179055603e55603c5560005b86518110156109a8576001600160a01b036107b98289610dc7565b51511690898214610997576000546001600160a01b031682146109865781600052603f6020526040600020541580159061096c575b61095b5760006040610800838b610dc7565b5101516108ce575b6080610814838b610dc7565b510151926040610824848c610dc7565b510151916060610834858d610dc7565b5101514201908142116108b8576040519360808501936001600160401b038511868610176108a2576001976003956040528652602086019182526040860192835260608601938452600052603f6020526040600020945185555186850155516002840155519101550161079e565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60206108da838b610dc7565b51015160406108e9848c610dc7565b5101519081156109475706610938576020610904838b610dc7565b5101516040610913848c610dc7565b510151918215610924575004610808565b634e487b7160e01b81526012600452602490fd5b631d6396f160e31b8152600490fd5b634e487b7160e01b83526012600452602483fd5b6322708f3760e21b60005260046000fd5b5081600052603f60205260016040600020015415156107ee565b632f2873bb60e11b60005260046000fd5b6378852d5560e01b60005260046000fd5b50866109b381610df1565b6102a0526109c084610fb5565b6102c052519020918261026052519020806102805246610220526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152610a2b60c082610d90565b5190206102005230610240527f82186402b6a35725297ac7691ca0a6e5be47eef3d200a5bc491a4e89d99cadde6102e0527fbda4078728c0ae5140c1cd85bf1abff4d86904b62dc8d2823ae88a05b34e2e37610300527f66a29e1886f085dbd8ec4fc1f6b13da644ad0efad4477114339037860a802dfe6103205260a052600160605560006061556080556000607f556001606255604051614a11908161115482396080518181816104bf01528181610f760152818161118101528181611a0601528181611e180152818161265f01528181612b5e01528181612d5d0152818161315d01528181613306015281816136a20152613841015260a0518181816105a50152818161081501528181610b4f01528181610e1001528181611554015281816115ea015281816116d00152818161171e01528181611abe01528181611c7401528181611d8101528181611e9901528181611f550152611fe2015260c051818181610d57015261145f015260e051818181610c090152611f12015261010051818181610d2f0152611a42015261012051818181610b13015261192c0152610140518181816117dd0152611a7d01526101605181818161132901526119040152610180518181816106730152610a6201526101a051818181610a3a0152610db401526101c05181818161108501528181613f9b015261417101526101e05181818161085e01528181610b98015281816114f60152611767015261020051816143520152610220518161440f01526102405181614323015261026051816143a1015261028051816143c701526102a0518161123701526102c0518161126301526102e0518161281c01526103005181613083015261032051816132150152f35b639249f17560e01b60005260046000fd5b631a0deb8760e31b60005260046000fd5b63939cd46160e01b60005260046000fd5b63526e939f60e11b60005260046000fd5b6040513d6000823e3d90fd5b633eb9379d60e01b60005260046000fd5b631e4fbdf760e01b600052600060045260246000fd5b60a084830312610d8b576040519060a08201906001600160401b038211838310176108a25760a092602092604052610d5887610db3565b815282870151838201526040870151604082015260608701516060820152608087015160808201528152019301926100c1565b600080fd5b601f909101601f19168101906001600160401b038211908210176108a257604052565b51906001600160a01b0382168203610d8b57565b8051821015610ddb5760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b90815160208110600014610e89575090601f815111610e2d576020815191015160208210610e1d571790565b6000198260200360031b1b161790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b838110610e715750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001610e4f565b6001600160401b0381116108a257604154600181811c91168015610fab575b6020821014610f9557601f8111610f5f575b50602092601f8211600114610efa5792819293600092610eef575b50508160011b916000199060031b1c19161760415560ff90565b015190503880610ed5565b601f198216936041600052806000209160005b868110610f475750836001959610610f2e575b505050811b0160415560ff90565b015160001960f88460031b161c19169055388080610f20565b91926020600181928685015181550194019201610f0d565b6041600052601f6020600020910160051c810190601f830160051c015b818110610f895750610eba565b60008155600101610f7c565b634e487b7160e01b600052602260045260246000fd5b90607f1690610ea8565b9081516020811060001461103d575090601f815111610fe1576020815191015160208210610e1d571790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b8381106110255750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001611003565b6001600160401b0381116108a257604254600181811c91168015611149575b6020821014610f9557601f8111611113575b50602092601f82116001146110ae57928192936000926110a3575b50508160011b916000199060031b1c19161760425560ff90565b015190503880611089565b601f198216936042600052806000209160005b8681106110fb57508360019596106110e2575b505050811b0160425560ff90565b015160001960f88460031b161c191690553880806110d4565b919260206001819286850151815501940192016110c1565b6042600052601f6020600020910160051c810190601f830160051c015b81811061113d575061106e565b60008155600101611130565b90607f169061105c56fe6080604052600436101561001257600080fd5b60003560e01c806309a69f57146120415780630aa4353314611fc457806314596a8214611f9757806315e3a6dc14611f7957806320dd50df14611f3557806321ea1bc414611efa57806322c45b6714611ece5780632420865714611e3d5780632c4eb8b214611db35780632e4edf1714611d65578063301a3e9514611cfe57806334238dd814611c59578063370f82fe14611c3b578063428d0b2514611b4657806342ba8c8914611aa057806342c9b7dd14611a6557806346b9c06914611a2a57806348efbf33146119e65780634abdf2e0146119985780634ec1b85814611971578063549013be14611705578063564ac5f514611674578063567b60be14611648578063656e84301461161c578063663ab7e1146115ce57806366ba3392146115b0578063677e0b271461151957806367936d13146114de5780636f4a2cd0146114c5578063715018a614611482578063725decc41461144757806379ba5097146113735780637ef6e8bd1461134c57806380e5deb01461131157806384b0196e1461121f5780638da5cb5b146111f85780638f97e3a0146111d05780639043ec6a146110da57806391eedf39146110a8578063990717441461106d5780639c82e39014610fc75780639f91c58d14610f9b578063abde825414610f11578063b03b65c414610e41578063b1b2f6eb14610df5578063b7fd4a6714610dd7578063b80adc5c14610d9c578063c04a6a4f14610b36578063ca26930014610afb578063cc37f730146107fc578063ce325bf8146106b4578063d179bd2214610696578063dad74b3f1461065b578063dc9139761461063d578063e30c397814610616578063e49f858a14610582578063ef772a6014610564578063f2b6c74914610418578063f2fde38b146102e15763fc65baaf146102b057600080fd5b346102dc5760206003193601126102dc5760043560005260026020526020604060002054604051908152f35b600080fd5b346102dc5760206003193601126102dc576001600160a01b036103026120b1565b61030a613f82565b1680156103ee576001600160a01b03603b541681146103c4576001600160a01b03603a5416811461039a5761033d613f82565b807fffffffffffffffffffffffff000000000000000000000000000000000000000060015416176001556001600160a01b03600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b7f939cd4610000000000000000000000000000000000000000000000000000000060005260046000fd5b7f9249f1750000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa4dd273e0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc576104316120b1565b6024356001600160a01b03603a54163314158061054f575b8061053a575b6105105761045c816140d8565b603e54918282116104e657610474826104e494612573565b603e55604051828152600160208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b005b7f6643a25f0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f2636942f0000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b036000541633141561044f565b506001600160a01b03603b5416331415610449565b346102dc5760006003193601126102dc576020606154604051908152f35b346102dc5760206003193601126102dc5761059b6120b1565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576001600160a01b0316600052607e6020526020604060002054604051908152f35b7f8b2731aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206001600160a01b0360015416604051908152f35b346102dc5760006003193601126102dc576020603c54604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc576020603e54604051908152f35b346102dc5760206003193601126102dc576040516106d181612222565b600081526000602082015260006040820152600060608201526080604051916106f98361223e565b60008352600060208401520152600435600052605f60205260406000206001600160a01b036003820154169081156107d2576001600160a01b036040519261074084612222565b82548452600183015492602085019384526107706004600283015492604088019384526060880194855201613882565b936080860194855260405195518652516020860152516040850152511660608301525190815160068110156107bc5760c092602091608084015201516107b5816121b7565b60a0820152f35b634e487b7160e01b600052602160045260246000fd5b7fd9e125150000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5761080a3661212c565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576004811015610ad15780600052603960205261085c60406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000000908162278d00029162278d008304036109e05710610aa75761089d61416c565b8060005260056020526040600020548211600014610a8a578060005260056020526108cd60406000205483612573565b8160005260066020526040600020548311908115610a60575b8115610a36575b50610a0c578060005260396020524260406000205560405161090e81612206565b8181528260208201524260408201526036549060048210156109f657600361094f920260250190604060029180518455602081015160018501550151910155565b603654906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086036558160005260056020526040600020546040519260808452600a60808501527f50656e616c74794665650000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b7f573a59ba0000000000000000000000000000000000000000000000000000000060005260046000fd5b90507f000000000000000000000000000000000000000000000000000000000000000010836108ed565b7f0000000000000000000000000000000000000000000000000000000000000000841091506108e6565b806000526005602052610aa282604060002054612573565b6108cd565b7fc2c1e97b0000000000000000000000000000000000000000000000000000000060005260046000fd5b7ffc87de8c0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc57610b443661212c565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576006811015610ad157806000526037602052610b9660406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000000908162278d00029162278d008304036109e05710610aa757610bd7613f96565b8060005260036020526040600020548211600014610d7f57806000526003602052610c0760406000205483612573565b7f00000000000000000000000000000000000000000000000000000000000000008311908115610d55575b8115610d2b575b50610a0c5780600052603760205242604060002055604051610c5a81612206565b8181528260208201524260408201526032549060068210156109f6576003610c9b920260070190604060029180518455602081015160018501550151910155565b60325490600182018092116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f092600660c093066032558160005260036020526040600020546040519260808452600960808501527f54696572426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f00000000000000000000000000000000000000000000000000000000000000001083610c39565b7f000000000000000000000000000000000000000000000000000000000000000084109150610c32565b806000526003602052610d9782604060002054612573565b610c07565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc576020606054604051908152f35b346102dc5760206003193601126102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576104e4600435613792565b346102dc5760206003193601126102dc576001600160a01b03610e626120b1565b610e6a613f82565b1680156103ee576001600160a01b0360005416811461039a576001600160a01b03603b54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603a541617603a55337f6d0f78b3a4ae2472e08ee885ed47bfb1ace996c12d6ff019537c99821244de8d600080a3005b7fd06f5c380000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576104e4600435610f3481603e546122b5565b603e55604051818152600160208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b346102dc5760206003193601126102dc5760043560005260046020526020604060002054604051908152f35b346102dc5760206003193601126102dc576001600160a01b03610fe86120b1565b610ff0613f82565b1680156103ee576001600160a01b036000541681146103c4576001600160a01b03603a54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603b541617603b55337f786bbb915c47f5cb032b19e70cd81f673f1ee5c1ac711db6681470264d521125600080a3005b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5733600052607e6020526020600160406000200154607d541115604051908152f35b346102dc5760406003193601126102dc576110f36120b1565b6024356001600160a01b03603a5416331415806111bb575b806111a6575b6105105761111e816140d8565b603d54918282116104e657611136826104e494612573565b603d55604051828152600260208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b506001600160a01b0360005416331415611111565b506001600160a01b03603b541633141561110b565b346102dc5760206003193601126102dc5760206111ee6004356135de565b6040519015158152f35b346102dc5760006003193601126102dc5760206001600160a01b0360005416604051908152f35b346102dc5760006003193601126102dc5760e061125b7f000000000000000000000000000000000000000000000000000000000000000061453e565b61130d6112877f00000000000000000000000000000000000000000000000000000000000000006146a2565b916112ec6020936112de6040519361129f878661225a565b6000855260003681376040519788977f0f00000000000000000000000000000000000000000000000000000000000000895288015260e0870190612142565b908582036040870152612142565b90466060850152306080850152600060a085015283820360c0850152612183565b0390f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5760206001600160a01b03603b5416604051908152f35b346102dc5760006003193601126102dc57600154336001600160a01b03821603611419577fffffffffffffffffffffffff000000000000000000000000000000000000000016600155600054337fffffffffffffffffffffffff00000000000000000000000000000000000000008216176000556001600160a01b033391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5761149b613f82565b7f23dbc96b0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576104e46133ee565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760406003193601126102dc5760243567ffffffffffffffff81116102dc5761154a9036906004016120c7565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163314611586576104e4916004356131f5565b7f163156450000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576020608054604051908152f35b346102dc576115dc366120f5565b906001600160a01b039392937f0000000000000000000000000000000000000000000000000000000000000000163314611586576020936111ee93613058565b346102dc5760206003193601126102dc5760043560005260066020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043560005260056020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc5736602460608402830101116102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec5760209160246111ee9201612c92565b346102dc576117133661212c565b906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec576004811015610ad15780600052603860205261176560406000205442612573565b7f00000000000000000000000000000000000000000000000000000000000000009062278d0082029180830462278d0014901517156109e05710610aa7576117ab613f96565b8060005260046020526040600020548211600014611954578060005260046020526117db60406000205483612573565b7f0000000000000000000000000000000000000000000000000000000000000000831190811561192a575b8115611900575b50610a0c578060005260386020524260406000205560405161182e81612206565b8181528260208201524260408201526034549060048210156109f657600361186f920260190190604060029180518455602081015160018501550151910155565b603454906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086034558160005260046020526040600020546040519260808452600960808501527f506c616e426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f0000000000000000000000000000000000000000000000000000000000000000108361180d565b7f000000000000000000000000000000000000000000000000000000000000000084109150611806565b80600052600460205261196c82604060002054612573565b6117db565b346102dc5760006003193601126102dc5760206001600160a01b03603a5416604051908152f35b346102dc5760206003193601126102dc576004356119b4613f82565b806040556040519081527f075e0c6585fd90d164d2f7f5d5399e97900ed892807821f47be4d64c9eb5238160203392a2005b346102dc5760006003193601126102dc5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760206003193601126102dc576004356001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec57606154611b1c576020817fe056d7ccce0301fe61ac060fe377a3c89b179cd09fa62ef48618d5791a78d64892607f55604051908152a1005b7fac5cadc30000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576001600160a01b03611b676120b1565b16600052603f6020526060604060002060405190611b84826121d4565b80549081835260018101549260208101938452600360028301549260408301938452015494859101528192600094804211611c04575b509360209481611bcf575b8585604051908152f35b51611bf39450611bed92919080821015611bfc5750905b51906121c1565b906122b5565b82808080611bc5565b905090611be6565b611c12620151809142612573565b0460018101809111611c275794506020611bba565b602486634e487b7160e01b81526011600452fd5b346102dc5760006003193601126102dc576020603d54604051908152f35b346102dc5760006003193601126102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec57606254600214611cd4576002606255426063557faeb375a1784645db3075ba87abdf2952280755ad1967e93aea416f217386a1bb600080a1005b7ff62324030000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc5760043560068110156102dc576024359060048210156102dc57611d31826121b7565b6006820291808304600614901517156109e057611d4d916122b5565b60188110156109f65760209060640154604051908152f35b346102dc57611d73366120f5565b906001600160a01b039392937f0000000000000000000000000000000000000000000000000000000000000000163314611586576020936111ee936127ed565b346102dc5760206003193601126102dc576104e4600435611dd681603c546122b5565b603c55604051818152600060208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc573660248360061b830101116102dc576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec5760209160246111ee9201612580565b346102dc5760206003193601126102dc5760043560005260036020526020604060002054604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346102dc5760006003193601126102dc576020604054604051908152f35b346102dc5760806003193601126102dc576104e4611fb36120b1565b60643590604435906024359061231f565b346102dc5760206003193601126102dc576004356001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036105ec5760207ff1649f58e4104d40851477a70db43e4ed6979ae52986533c3bc43abb6642dcfa916120346138c5565b80608055604051908152a1005b346102dc5760006003193601126102dc57612069336000526044602052604060002054151590565b156120875733600052607e6020526020604060002054604051908152f35b7f72a7bddc0000000000000000000000000000000000000000000000000000000060005260046000fd5b600435906001600160a01b03821682036102dc57565b9181601f840112156102dc5782359167ffffffffffffffff83116102dc57602083818601950101116102dc57565b60606003198201126102dc5760043591602435916044359067ffffffffffffffff82116102dc57612128916004016120c7565b9091565b60031960409101126102dc576004359060243590565b919082519283825260005b84811061216e575050601f19601f8460006020809697860101520116010190565b8060208092840101518282860101520161214d565b906020808351928381520192019060005b8181106121a15750505090565b8251845260209384019390920191600101612194565b600411156107bc57565b818102929181159184041417156109e057565b6080810190811067ffffffffffffffff8211176121f057604052565b634e487b7160e01b600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176121f057604052565b60a0810190811067ffffffffffffffff8211176121f057604052565b6040810190811067ffffffffffffffff8211176121f057604052565b90601f601f19910116810190811067ffffffffffffffff8211176121f057604052565b60405191906000835b6018821061229f5750505061229d6103008361225a565b565b6001602081928554815201930191019091612286565b919082018092116109e057565b9060188110156109f65760051b0190565b60188210156109f6570190600090565b80548210156109f65760005260206000209060011b0190600090565b8115612309570490565b634e487b7160e01b600052601260045260246000fd5b6001600160a01b0390939192936123346138c5565b169261234d846000526044602052604060002054151590565b156125005783600052607e602052600160406000200154607d54111561250057836000526045602052612383604060002061227d565b906000936006810290808204600614901517156124ec576123ae916123a7916122b5565b80926122c2565b51806123fa575b50505081600052607e6020526123d160406000209182546122b5565b90557fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b848452607e60205261242361241583600260408820016122d3565b90549060031b1c93846122b5565b92828552607c60205260408520548410156124d8575b905b8382106124825750506124616124789185600052607e60205260026040600020016122d3565b81939154906000199060031b92831b921b19161790565b90553880806123b5565b90936124d060019184600052607c602052611bed6124b1846124a88a60406000206122e3565b500154866121c1565b86600052607c6020526124c88960406000206122e3565b5054906122ff565b94019061243b565b828552607c60205260408520549350612439565b602485634e487b7160e01b81526011600452fd5b50505050565b91908110156109f65760061b0190565b67ffffffffffffffff81116121f05760051b60200190565b9061253882612516565b612545604051918261225a565b828152601f196125558294612516565b0190602036910137565b80518210156109f65760209160051b010190565b919082039182116109e057565b919080156127645760005b81811061271e575061259b6133ee565b6125b2336000526044602052604060002054151590565b80612702575b6126d457916125c68361252e565b600093849285915b808310612689575050506126407f6662f35bbdebb39ebf472c94252ecf0dd0c55fc8d0a922e0595b12c53d643937918561261761260f6126849798876122b5565b606154612573565b60615561262681603d546122b5565b603d55815190604051938493608085526080850190612183565b91602084015285604084015260608301520390a1336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b600190565b909193956126cb6001916126c56126a1888688612506565b916126b26020843594013584613c29565b9190936126bf8b8b61255f565b526122b5565b986122b5565b940191906125ce565b509050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106125b8565b602061272b828487612506565b01351561273a5760010161258b565b7f115e63aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f930363960000000000000000000000000000000000000000000000000000000060005260046000fd5b67ffffffffffffffff81116121f057601f01601f191660200190565b9291926127b68261278e565b916127c4604051938461225a565b8294818452818301116102dc578281602093846000960137010152565b60068210156107bc5752565b93929190916000918315612c5a5761287f916128706128769261286889604051612860816128528c60208301957f0000000000000000000000000000000000000000000000000000000000000000876040919493926060820195825260208201520152565b03601f19810183528261225a565b519020613f07565b9236916127aa565b90614435565b90929192614471565b6001600160a01b0333911603612c32576128976133ee565b6128ae336000526044602052604060002054151590565b80612c18575b612bec576128c1846121b7565b604051936128ce8561223e565b81855260208501946128df826121b7565b81865251946006861015612bd857516128f7816121b7565b612900816121b7565b6040519561290d87612206565b8487526020870181905260408701918252906006821015612bc45751612932816121b7565b612948604051926129428461223e565b836127e1565b612951816121b7565b60208201526060549250855195600460405161296c81612222565b858152602081019889526001600160a01b03604082014281526060830190338252608084019b878d5289600052605f60205260406000209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff000000000000000000000000000000000000000082541617905501965196875160068110156107bc576000987fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612a32816121b7565b612a3b816121b7565b60081b16931691161717905580513388526046602052612a60604089209182546122b5565b90558051612a6d8361421e565b6018811015612bb05791612aac91612a8d612ad3946064019182546122b5565b905551923389526045602052612aa660408a209161421e565b906122d3565b612abf8294925492838360031b1c6122b5565b91906000199060031b92831b921b19161790565b9055612ade336148f1565b612b83575b612aef836061546122b5565b60615560605460018101809111611c2757612684949550606055612b12816121b7565b604051918252826020830152612b27816121b7565b60408201527f8629b9bbe2698bb9733aab9759f36ec9390475da11e15309b08e8755ea5862fd60603392a230336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b338552607e602052612b9a60026040872001613f48565b338552607e602052426001604087200155612ae3565b602489634e487b7160e01b81526032600452fd5b602484634e487b7160e01b81526021600452fd5b602483634e487b7160e01b81526021600452fd5b91925050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b08280a290565b50607d54338252607e6020526001604083200154106128b4565b807f710651b70000000000000000000000000000000000000000000000000000000060049252fd5b6004837f905091fe000000000000000000000000000000000000000000000000000000008152fd5b91908110156109f6576060020190565b9190801561302e5760005b818110612feb5750612cad6133ee565b612cc4336000526044602052604060002054151590565b80612fcf575b6126d457600090612cda8161252e565b93825b828110612d82575050612d42612684939482612d1c7f254fa5b38abff01b7e340b6d943b8e9c17ff5a3f7fe4a447dc3e92ffb3705a0e946060546122b5565b606055612d2b856061546122b5565b606155604051928392606084526060840190612183565b9060208301528460408301520390a130336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613ead565b612d8d818484612c82565b93612d9a853580926122b5565b946060813603126102dc5760405191612db283612206565b82526020820190602081013582526040808401910135815260009151906006821015612bd85751612de2816121b7565b612df2604051926129428461223e565b612dfb816121b7565b6020820152612e0c846060546122b5565b9280516004604051612e1d81612222565b868152602081019283526001600160a01b0360408201428152606083019033825260808401958887528a8a52605f60205260408a209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790550190518051906006821015612fbb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612ede816121b7565b612ee7816121b7565b60081b16931691161717905580513384526046602052612f0c604085209182546122b5565b90558051612f198361421e565b6018811015612fa75760019695949392612f5892612f3f612aac936064019182546122b5565b905551923385526045602052612aa6604086209161421e565b9055612f63336148f1565b612f7a575b50612f73828961255f565b5201612cdd565b338152607e602052612f9160026040832001613f48565b338152607e602052836040429220015538612f68565b602485634e487b7160e01b81526032600452fd5b602486634e487b7160e01b81526021600452fd5b50607d5433600052607e60205260016040600020015410612cca565b612ff6818386612c82565b351561300457600101612c9d565b7f905091fe0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f80ef666a0000000000000000000000000000000000000000000000000000000060005260046000fd5b91929092831561273a576130b991612870612876926128686040516020810190612860816128528c8c7f0000000000000000000000000000000000000000000000000000000000000000876040919493926060820195825260208201520152565b6001600160a01b03339116036131cb576130d16133ee565b6130e8336000526044602052604060002054151590565b806131af575b613182576130ff6126849282613c29565b909161310e61260f83856122b5565b60615561311d82603d546122b5565b603d556040519183835260208301527f277a1a847b4895ea9853b0f19969c04b8d17cb55b9ad4504179ede80fbf0f7af60403393a3336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b5050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106130ee565b7f710651b70000000000000000000000000000000000000000000000000000000060005260046000fd5b919082156133c45761324b916128706128769261286860405160208101907f000000000000000000000000000000000000000000000000000000000000000082528860408201526040815261286060608261225a565b6001600160a01b03339116036131cb576132636133ee565b61327a336000526044602052604060002054151590565b806133a8575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206132b2828254612573565b9055603c549080821061332b576132cc8161229d93612573565b603c556040518181527fecf9667816c0bdb34ff25e5437430e488cebc3841c3a2c90e3927e07afcd78b460203392a2336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b7f4aa330410000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa689c4650000000000000000000000000000000000000000000000000000000060005260046000fd5b50337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2565b50607d5433600052607e60205260016040600020015410613280565b7f8f9ba5d50000000000000000000000000000000000000000000000000000000060005260046000fd5b6133f66138c5565b61340d336000526044602052604060002054151590565b1561229d5733600052607e602052600160406000200154607d54111561229d57336000526045602052613443604060002061227d565b60009060029060005b600481106134b95750506002146134a1575b33600052607e60205261347760406000209182546122b5565b9055337fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b33600052607e6020524260016040600020015561345e565b60005b600681106134cd575060010161344c565b60068202828104600614831517156109e057816134e9916122b5565b6134f381856122c2565b519081156135d45733600052607e6020526135158160026040600020016122d3565b90549060031b1c916102dc83018084116109e0576000838152607c602052604090205481106135ca575081600052607c602052604060002054925b905b8382106135855750509061357c6124616001949333600052607e60205260026040600020016122d3565b90555b016134bc565b90976135c2600191611bed6135ab846124a88e89600052607c60205260406000206122e3565b86600052607c6020526124c88d60406000206122e3565b980190613552565b6001975092613550565b505060019061357f565b33600052603f60205260406000206040516135f8816121d4565b815481526060600183015491602081019283526003600285015494604083019586520154918291015260009080421161375c575b5090816136cd575b50505033600052603f60205280604060002054106136c7576126849033600052603f6020526040600020613669828254612573565b90556040518181527fcf93002ecae3f252116b013a1ae8ec072919c19ab134261c065cc94d5b0b3e5860203392a2336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b50600090565b518082101561375557505b8062015180029162015180830482036109e0576137329233600052603f60205261370b60036040600020019182546122b5565b905533600052603f6020526001604060002001613729838254612573565b905551906121c1565b33600052603f60205261374b60406000209182546122b5565b9055388080613634565b90506136d8565b61376a620151809142612573565b04906001820180921161377e57503861362c565b80634e487b7160e01b602492526011600452fd5b80156133c4576137a06133ee565b6137b7336000526044602052604060002054151590565b80613866575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206137ef828254612573565b9055603c549080821061332b576138098161229d93612573565b603c557f916496170aac2ab5c6b8c47b015f9a422bab4f77114f70a76c7289a2e7312eb46020604051838152a1336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016613e58565b50607d5433600052607e602052600160406000200154106137bd565b9060405161388f8161223e565b602060ff8294546138a2828216856127e1565b60081c16916138b0836121b7565b0152565b60ff1660ff81146109e05760010190565b60625460011461229d57606354421061229d576080541561229d576138e8613f96565b620151806138f860635442612573565b04600101806001116109e057600060189261391284612516565b90613920604051928361225a565b848252601f1961392f86612516565b0136602084013760405191606484845b888210613c12575050506139556103008461225a565b835b60ff8116946004861015613a365795926000906006870298878a0460061488151715985b60ff84166006811015613a19578a6109e057613997818d6122b5565b908a8a6139a484826122c2565b5115613a0b57926127106139f56126bf946139e0613a0598956139ca866139ff9a6122c2565b51906000526004602052604060002054906121c1565b906000526003602052604060002054906121c1565b049283918b61255f565b936138b4565b9261397b565b5050505092613a05906138b4565b50959750985092965050613a2c906138b4565b9490919394613957565b919592969450508015613be0576080549160005b60ff81166004811015613b8257801560068281029283041417159860005b60ff81166006811015613b6f578b6109e0578787613a91613a8a8e94886122b5565b809461255f565b518015613b625790613ac6613acb92613ac18b60405196613ab18861223e565b60008852600060208901526121c1565b6121c1565b6122ff565b9060208101918252613add838c6122c2565b51815282600052607c60205260406000208054680100000000000000008110156121f057613b10916001820181556122e3565b929092613b4c5760019151835551910155898110156109f6576047019081549160001983146109e0576001613b47930190556138b4565b613a68565b634e487b7160e01b600052600060045260246000fd5b50505050613b47906138b4565b5050985050613b7d906138b4565b613a4a565b505050505050505042607d55613b9d62015180420642612573565b6201518081018091116109e057607f54613bb6916122b5565b6063557f040bde66f3369b7fc094e7401b5de26a5be6900e0fb645651d59eb57b8620dbc600080a1565b5050505050613bf462015180420642612573565b6201518081018091116109e057607f54613c0d916122b5565b606355565b82548152600192830192919091019060200161393f565b919091613c3461416c565b80600052605f6020526040600020906001600160a01b0360038301541680156107d25784600184015410613e2e573303613e045760009380926004600282015491019060ff825460081c1690613c89826121b7565b816000526002602052604060002054620151808102908082046201518014901517156109e057613cb99042612573565b10613dc9575b503360005260466020526040600020613cd9838254612573565b9055613cec613ce782613882565b61421e565b60188110156109f657613d3b91613d2791606401613d0b858254612573565b9055336000526045602052612aa6613ce7604060002092613882565b8192915490612abf85838360031b1c612573565b905533600052604660205260406000205415613db7575b81600052605f602052613d6e6001604060002001918254612573565b905580600052605f60205260016040600020015415613d8c57509190565b600052605f602052600060046040822082815582600182015582600282015582600382015501559190565b613dc03361478c565b613d5257600080fd5b9195509250613dd7816121b7565b6000526005602052612710613df1604060002054866121c1565b0493613dfd8582612573565b9238613cbf565b7f387224d40000000000000000000000000000000000000000000000000000000060005260046000fd5b7fe0a6f7200000000000000000000000000000000000000000000000000000000060005260046000fd5b61229d926001600160a01b03604051937fa9059cbb000000000000000000000000000000000000000000000000000000006020860152166024840152604483015260448252613ea860648361225a565b61425f565b9091926001600160a01b0361229d9481604051957f23b872dd000000000000000000000000000000000000000000000000000000006020880152166024860152166044840152606483015260648252613ea860848361225a565b604290613f12614319565b90604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b60478114613f7f57604790604754906000915b60188310613f695750505050565b6001809194019283549481840155019192613f5b565b50565b6001600160a01b0360005416330361141957565b6031547f0000000000000000000000000000000000000000000000000000000000000000801562278d0080830292830414171592915b60325481146140d05760068110156109f6576003810284613ff1600983015442612573565b906109e0578310156140425760009060088101549150600701546000526003602052604060002055603154600181018091116109e05760069006603155600181018091116109e05760069006613fcc565b505091905b6033545b60345481146140ca5760048110156109f6576003810283614070601b83015442612573565b906109e0578510156140c357600090601a81015491506019015460005260046020526040600020556033546001810181116109e0576001600491086033556001810181116109e05760016004910861404b565b5050915050565b50915050565b509190614047565b801561414257604054106140e857565b6001600160a01b03600054163314158061412d575b61410357565b7f1c2a55280000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b03603b54163314156140fd565b7f11184a7b0000000000000000000000000000000000000000000000000000000060005260046000fd5b6035547f00000000000000000000000000000000000000000000000000000000000000009162278d0083029280840462278d001490151715915b60365481146140ca5760048110156109f65760038102836141cb602783015442612573565b906109e0578510156140c357600090602681015491506025015460005260056020526040600020556035546001810181116109e0576001600491086035556001810181116109e0576001600491086141a6565b60208101519061422d826121b7565b614236826121b7565b6006820291808304600614901517156109e0575160068110156107bc5761425c916122b5565b90565b6000806001600160a01b036142aa93169360208151910182865af13d15614311573d9061428b8261278e565b91614299604051938461225a565b82523d6000602084013e5b8361494b565b80519081151591826142ed575b50506142c05750565b7f5274afe70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b81925090602091810103126102dc57602001518015908115036102dc5738806142b7565b6060906142a4565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630148061440c575b15614374577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a0815261440660c08261225a565b51902090565b507f0000000000000000000000000000000000000000000000000000000000000000461461434b565b81519190604183036144665761445f92506020820151906060604084015193015160001a9061485c565b9192909190565b505060009160029190565b61447a816121b7565b80614483575050565b61448c816121b7565b600181036144be577ff645eedf0000000000000000000000000000000000000000000000000000000060005260046000fd5b6144c7816121b7565b600281036144fd57507ffce698f70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b600390614509816121b7565b146145115750565b7fd78bce0c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60ff81146145a25760ff811690601f8211614578576040805192614562828561225a565b60208452601f1960208501920136833783525290565b7fb3512b0c0000000000000000000000000000000000000000000000000000000060005260046000fd5b506040516000604154908160011c91600181168015614698575b60208410811461468457838552849291811561464757506001146145e7575b61425c9250038261225a565b506041600090815290917f7c9785e8241615bc80415d89775984a1337d15dc1bf4ce50f41988b2a2b336a75b81831061462b57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614613565b6020925061425c9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016145db565b602483634e487b7160e01b81526022600452fd5b92607f16926145bc565b60ff81146146c65760ff811690601f8211614578576040805192614562828561225a565b506040516000604254908160011c9160018116801561476a575b602084108114614684578385528492918115614647575060011461470a5761425c9250038261225a565b506042600090815290917f38dfe4635b27babeca8be38d3b448cb5161a639b899a14825ba9c8d7892eb8c35b81831061474e57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614736565b92607f16926146e0565b80548210156109f65760005260206000200190600090565b60008181526044602052604090205480156148555760001981018181116109e0576043549060001982019182116109e05781810361481b575b505050604354801561480557600019016147e0816043614774565b60001982549160031b1b19169055604355600052604460205260006040812055600190565b634e487b7160e01b600052603160045260246000fd5b61483d61482c612461936043614774565b90549060031b1c9283926043614774565b905560005260446020526040600020553880806147c5565b5050600090565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116148e5579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa156148d9576000516001600160a01b038116156148cd5790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b50505060009160039190565b806000526044602052604060002054156000146136c757604354680100000000000000008110156121f0576149326124618260018594016043556043614774565b9055604354906000526044602052604060002055600190565b9061498a575080511561496057805190602001fd5b7f1425ea420000000000000000000000000000000000000000000000000000000060005260046000fd5b815115806149d2575b61499b575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b50803b1561499356fea264697066735822122029bc36c2c231c119492b3ac9d5378cd3e143f3f82f63c8339cc7048bd177894764736f6c634300081a00336101606040523461051257604051610018604082610517565b6008815260208101672d27222a37b5b2b760c11b81526040519061003d604083610517565b60088252672d27222a37b5b2b760c11b602083015260405192610061604085610517565b600384526216939160ea1b602085015260405193610080604086610517565b600185526020850193603160f81b855233156104fc57600680546001600160a01b03199081169091556000805433928116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a38051906001600160401b0382116103f95760045490600182811c921680156104f2575b60208310146103d95781601f849311610482575b50602090601f831160011461041a5760009261040f575b50508160011b916000199060031b1c1916176004555b8051906001600160401b0382116103f95760055490600182811c921680156103ef575b60208310146103d95781601f849311610385575b50602090601f831160011461031d57600092610312575b50508160011b916000199060031b1c1916176005555b6101b48161053a565b610120526101c1846106e8565b61014052519020918260e05251902080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261022a60c082610517565b5190206080523060c05260006003546b024306c4097859c43c00000081018091116102fe576003555033600052600160205260406000206b024306c4097859c43c00000081540190556040516b024306c4097859c43c000000815260007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203393a36040516113c69081610887823960805181610efe015260a05181610fbb015260c05181610ecf015260e05181610f4d01526101005181610f730152610120518161058b015261014051816105b40152f35b634e487b7160e01b82526011600452602482fd5b015190503880610195565b600560009081528281209350601f198516905b81811061036d5750908460019594939210610354575b505050811b016005556101ab565b015160001960f88460031b161c19169055388080610346565b92936020600181928786015181550195019301610330565b90915060056000526020600020601f840160051c810191602085106103cf575b90601f859493920160051c01905b8181106103c0575061017e565b600081558493506001016103b3565b90915081906103a5565b634e487b7160e01b600052602260045260246000fd5b91607f169161016a565b634e487b7160e01b600052604160045260246000fd5b015190503880610131565b600460009081528281209350601f198516905b81811061046a5750908460019594939210610451575b505050811b01600455610147565b015160001960f88460031b161c19169055388080610443565b9293602060018192878601518155019501930161042d565b60046000529091507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f840160051c810191602085106104e8575b90601f859493920160051c01905b8181106104d9575061011a565b600081558493506001016104cc565b90915081906104be565b91607f1691610106565b631e4fbdf760e01b600052600060045260246000fd5b600080fd5b601f909101601f19168101906001600160401b038211908210176103f957604052565b908151602081106000146105d2575090601f815111610576576020815191015160208210610566571790565b6000198260200360031b1b161790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b8381106105ba5750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001610598565b6001600160401b0381116103f957600754600181811c911680156106de575b60208210146103d957601f81116106a8575b50602092601f82116001146106435792819293600092610638575b50508160011b916000199060031b1c19161760075560ff90565b01519050388061061e565b601f198216936007600052806000209160005b8681106106905750836001959610610677575b505050811b0160075560ff90565b015160001960f88460031b161c19169055388080610669565b91926020600181928685015181550194019201610656565b6007600052601f6020600020910160051c810190601f830160051c015b8181106106d25750610603565b600081556001016106c5565b90607f16906105f1565b90815160208110600014610770575090601f815111610714576020815191015160208210610566571790565b6040519063305a27a960e01b8252602060048301528181519182602483015260005b8381106107585750508160006044809484010152601f80199101168101030190fd5b60208282018101516044878401015285935001610736565b6001600160401b0381116103f957600854600181811c9116801561087c575b60208210146103d957601f8111610846575b50602092601f82116001146107e157928192936000926107d6575b50508160011b916000199060031b1c19161760085560ff90565b0151905038806107bc565b601f198216936008600052806000209160005b86811061082e5750836001959610610815575b505050811b0160085560ff90565b015160001960f88460031b161c19169055388080610807565b919260206001819286850151815501940192016107f4565b6008600052601f6020600020910160051c810190601f830160051c015b81811061087057506107a1565b60008155600101610863565b90607f169061078f56fe608080604052600436101561001357600080fd5b60003560e01c90816306fdde031461095257508063095ea7b31461092c57806318160ddd1461090e57806323b872dd146108d6578063313ce567146108ba5780633644e5151461089757806342966c681461087a57806370a0823114610833578063715018a6146107f057806379ba50971461070257806379cc6790146106d25780637ecebe001461068b57806384b0196e146105725780638da5cb5b1461053e578063902d55a51461051f57806395d89b411461041c578063a9059cbb146103eb578063d505accf14610252578063dd62ed3e146101e0578063e30c3978146101ac5763f2fde38b1461010657600080fd5b346101a75760206003193601126101a75773ffffffffffffffffffffffffffffffffffffffff610134610a56565b61013c61108a565b16807fffffffffffffffffffffffff0000000000000000000000000000000000000000600654161760065573ffffffffffffffffffffffffffffffffffffffff600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b600080fd5b346101a75760006003193601126101a757602073ffffffffffffffffffffffffffffffffffffffff60065416604051908152f35b346101a75760406003193601126101a7576101f9610a56565b73ffffffffffffffffffffffffffffffffffffffff610216610a79565b9116600052600260205273ffffffffffffffffffffffffffffffffffffffff604060002091166000526020526020604060002054604051908152f35b346101a75760e06003193601126101a75761026b610a56565b610273610a79565b604435906064359260843560ff811681036101a7578442116103bd5761037661036d73ffffffffffffffffffffffffffffffffffffffff92838516978860005260096020526040600020908154916001830190556040519060208201927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c984528b6040840152878a1660608401528a608084015260a083015260c082015260c0815261032060e082610b91565b51902061032b610eb8565b90604051917f190100000000000000000000000000000000000000000000000000000000000083526002830152602282015260c43591604260a4359220611202565b909291926112a4565b1684810361038b575061038993506110ab565b005b84907f4b800e460000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b847f627913020000000000000000000000000000000000000000000000000000000060005260045260246000fd5b346101a75760406003193601126101a757610411610407610a56565b6024359033610d8b565b602060405160018152f35b346101a75760006003193601126101a757604051600060055461043e81610a9c565b80845290600181169081156104dd575060011461047e575b61047a8361046681850382610b91565b6040519182916020835260208301906109f7565b0390f35b600560009081527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0939250905b8082106104c357509091508101602001610466610456565b9192600181602092548385880101520191019092916104ab565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208086019190915291151560051b840190910191506104669050610456565b346101a75760006003193601126101a75760206040516329b927008152f35b346101a75760006003193601126101a757602073ffffffffffffffffffffffffffffffffffffffff60005416604051908152f35b346101a75760006003193601126101a75761062d6105af7f000000000000000000000000000000000000000000000000000000000000000061112c565b6105d87f00000000000000000000000000000000000000000000000000000000000000006111cb565b602061063b604051926105eb8385610b91565b6000845260003681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e08701906109f7565b9085820360408701526109f7565b466060850152306080850152600060a085015283810360c085015281808451928381520193019160005b82811061067457505050500390f35b835185528695509381019392810192600101610665565b346101a75760206003193601126101a75773ffffffffffffffffffffffffffffffffffffffff6106b9610a56565b1660005260096020526020604060002054604051908152f35b346101a75760406003193601126101a7576103896106ee610a56565b602435906106fd823383610c01565b610fe1565b346101a75760006003193601126101a7576006543373ffffffffffffffffffffffffffffffffffffffff8216036107c2577fffffffffffffffffffffffff000000000000000000000000000000000000000016600655600054337fffffffffffffffffffffffff000000000000000000000000000000000000000082161760005573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b346101a75760006003193601126101a75761080961108a565b7f343916260000000000000000000000000000000000000000000000000000000060005260046000fd5b346101a75760206003193601126101a75773ffffffffffffffffffffffffffffffffffffffff610861610a56565b1660005260016020526020604060002054604051908152f35b346101a75760206003193601126101a75761038960043533610fe1565b346101a75760006003193601126101a75760206108b2610eb8565b604051908152f35b346101a75760006003193601126101a757602060405160128152f35b346101a75760606003193601126101a7576104116108f2610a56565b6108fa610a79565b60443591610909833383610c01565b610d8b565b346101a75760006003193601126101a7576020600354604051908152f35b346101a75760406003193601126101a757610411610948610a56565b60243590336110ab565b346101a75760006003193601126101a757600060045461097181610a9c565b80845290600181169081156104dd57506001146109985761047a8361046681850382610b91565b600460009081527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b939250905b8082106109dd57509091508101602001610466610456565b9192600181602092548385880101520191019092916109c5565b919082519283825260005b848110610a415750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201610a02565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101a757565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036101a757565b90600182811c92168015610ae5575b6020831014610ab657565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691610aab565b60009291815491610aff83610a9c565b8083529260018116908115610b555750600114610b1b57505050565b60009081526020812093945091925b838310610b3b575060209250010190565b600181602092949394548385870101520191019190610b2a565b905060209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091509291921683830152151560051b010190565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610bd257604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff909291921691826000526002602052604060002073ffffffffffffffffffffffffffffffffffffffff8216600052602052604060002054927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8403610c7c575b50505050565b828410610d3f578015610d105773ffffffffffffffffffffffffffffffffffffffff821615610ce157600052600260205273ffffffffffffffffffffffffffffffffffffffff6040600020911660005260205260406000209103905538808080610c76565b7f94280d6200000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b7fe602df0500000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b5073ffffffffffffffffffffffffffffffffffffffff83917ffb8f41b2000000000000000000000000000000000000000000000000000000006000521660045260245260445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff16908115610e895773ffffffffffffffffffffffffffffffffffffffff16918215610e5a57600082815260016020526040812054828110610e275791604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9587602096526001865203828220558681526001845220818154019055604051908152a3565b6064937fe450d38c0000000000000000000000000000000000000000000000000000000083949352600452602452604452fd5b7fec442f0500000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b7f96c6fd1e00000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016301480610fb8575b15610f20577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a08152610fb260c082610b91565b51902090565b507f00000000000000000000000000000000000000000000000000000000000000004614610ef7565b73ffffffffffffffffffffffffffffffffffffffff168015610e89576000918183526001602052604083205481811061105857817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092858752600184520360408620558060035403600355604051908152a3565b83927fe450d38c0000000000000000000000000000000000000000000000000000000060649552600452602452604452fd5b73ffffffffffffffffffffffffffffffffffffffff6000541633036107c257565b73ffffffffffffffffffffffffffffffffffffffff16908115610d105773ffffffffffffffffffffffffffffffffffffffff16918215610ce15760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260028252604060002085600052825280604060002055604051908152a3565b60ff81146111ae5760ff811690601f82116111845760408051926111508285610b91565b602084527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208501920136833783525290565b7fb3512b0c0000000000000000000000000000000000000000000000000000000060005260046000fd5b506040516111c8816111c1816007610aef565b0382610b91565b90565b60ff81146111ef5760ff811690601f82116111845760408051926111508285610b91565b506040516111c8816111c1816008610aef565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411611298579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa1561128c5760005173ffffffffffffffffffffffffffffffffffffffff8116156112805790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b50505060009160039190565b919091600481101561136157806112ba57509050565b6000600182036112ee577ff645eedf0000000000000000000000000000000000000000000000000000000060005260046000fd5b506002810361132557827ffce698f70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b9091600360009214611335575050565b602492507fd78bce0c000000000000000000000000000000000000000000000000000000008252600452fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea2646970667358221220a60629b6927bbe3a89e220d5a83a29e9b89672a00d099ce3d4418135d052111564736f6c634300081a0033000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d812885994300000000000000000000000036ac695293d35bd90586b5353471c2004407ef6e00000000000000000000000041c31f690873ec5a642dd374e2f2dd1528d057300000000000000000000000000000000000000000000007b864634d3cc768000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6d34ae9275e1a900000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000036fdb8800968fb8b38d53ebce3c86f909c79637200000000000000000000000000000000000000000031ef6e017e6033e9a00000000000000000000000000000000000000000000000000000000000000000016800000000000000000000000000000000000000000000000000000000013c680000000000000000000000000000000000000000000006cf2f721f56aca1600000000000000000000000000000552002ec9504f3931019d63a8d992c98fcdf1eb70000000000000000000000000000000000000000003b41ea57328adb16b00000000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000000000000000000000000000000000000000ed4e0000000000000000000000000000000000000000000009a58bb5318a10ffd00000000000000000000000000000719d74be74980f94b2b9ee670f94f540661f9f6600000000000000000000000000000000000000000004a1de0780a6934f100000000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000076a700000000000000000000000000000000000000000000012870bb26f3ec17f000000000000000000000000000004c5e31774b09cf489c482a472bb78ae8c024c70100000000000000000000000000000000000000000003e41c59d913dabc00000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000bdbc41e0348b300000000000000000000000000000003ce0935e2f28339d6f9f1068356a90bf6d3173e3000000000000000000000000000000000000000000045348a87272a44a20000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000e2cb06bdfece5f6000000000000000000000000000009ff1299f5abea9ff1789c86e1c972ac1b3a8d38d0000000000000000000000000000000000000000000445f16bd0aef280c0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000004f1a0000000000000000000000000000000000000000000000f022435fc28028c00000000000000000000000000000ca4bde17f2a29fd041665cee2d206bc5c2ae507f00000000000000000000000000000000000000000004389a2f2eeb40b760000000000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000fd7980018631f2200000000000000000000000000000ae0a2dbdfdc0b9d1132b52796d5fcc9a19bd5dc300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004a1d89bb94865ec00000000000000000000000000000053423085f352a12f0b4c51e00c512e130ae02b3800000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000004a1d89bb94865ec000000000000000000000000000000af3bf534d2b026b9c72cb6fb30a5f7f879a6f4f1000000000000000000000000000000000000000000a7eaa0281559f34020000000000000000000000000000000000000000000000000000000000000000007080000000000000000000000000000000000000000000000000000000003b5380000000000000000000000000000000000000000000000004be4e7267b6ae00000000000000000000000000000344b16fe768582f69205897d240e2971b3ac0c09000000000000000000000000000000000000000000250ec5f358893873d0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000943b021e44ac2c4300000000000000000000000000000c5d2d690d683c6f7bb654e91fed293caec2a26b50000000000000000000000000000000000000000001851a42ee173aa2940000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000004a1e59e6490d2d9c0000000000000000000000000000018bf7f1cf41f840a7cb21618d5d1c1af7ca3e9080000000000000000000000000000000000000000002e524435ac3e59a8a0000000000000000000000000000000000000000000000000000000000000000004380000000000000000000000000000000000000000000000000000000001da9c00000000000000000000000000000000000000000000000031df9095a18f600000000000000000000000000000fdf5b0d1cd40223ef91027ce014cb4a31ac4ecf200000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000001bccb3fbc0c0014000000

Deployed Bytecode

0x6080604052600436101561001257600080fd5b60003560e01c806309a69f57146120415780630aa4353314611fc457806314596a8214611f9757806315e3a6dc14611f7957806320dd50df14611f3557806321ea1bc414611efa57806322c45b6714611ece5780632420865714611e3d5780632c4eb8b214611db35780632e4edf1714611d65578063301a3e9514611cfe57806334238dd814611c59578063370f82fe14611c3b578063428d0b2514611b4657806342ba8c8914611aa057806342c9b7dd14611a6557806346b9c06914611a2a57806348efbf33146119e65780634abdf2e0146119985780634ec1b85814611971578063549013be14611705578063564ac5f514611674578063567b60be14611648578063656e84301461161c578063663ab7e1146115ce57806366ba3392146115b0578063677e0b271461151957806367936d13146114de5780636f4a2cd0146114c5578063715018a614611482578063725decc41461144757806379ba5097146113735780637ef6e8bd1461134c57806380e5deb01461131157806384b0196e1461121f5780638da5cb5b146111f85780638f97e3a0146111d05780639043ec6a146110da57806391eedf39146110a8578063990717441461106d5780639c82e39014610fc75780639f91c58d14610f9b578063abde825414610f11578063b03b65c414610e41578063b1b2f6eb14610df5578063b7fd4a6714610dd7578063b80adc5c14610d9c578063c04a6a4f14610b36578063ca26930014610afb578063cc37f730146107fc578063ce325bf8146106b4578063d179bd2214610696578063dad74b3f1461065b578063dc9139761461063d578063e30c397814610616578063e49f858a14610582578063ef772a6014610564578063f2b6c74914610418578063f2fde38b146102e15763fc65baaf146102b057600080fd5b346102dc5760206003193601126102dc5760043560005260026020526020604060002054604051908152f35b600080fd5b346102dc5760206003193601126102dc576001600160a01b036103026120b1565b61030a613f82565b1680156103ee576001600160a01b03603b541681146103c4576001600160a01b03603a5416811461039a5761033d613f82565b807fffffffffffffffffffffffff000000000000000000000000000000000000000060015416176001556001600160a01b03600054167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e22700600080a3005b7f939cd4610000000000000000000000000000000000000000000000000000000060005260046000fd5b7f9249f1750000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa4dd273e0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc576104316120b1565b6024356001600160a01b03603a54163314158061054f575b8061053a575b6105105761045c816140d8565b603e54918282116104e657610474826104e494612573565b603e55604051828152600160208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b005b7f6643a25f0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f2636942f0000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b036000541633141561044f565b506001600160a01b03603b5416331415610449565b346102dc5760006003193601126102dc576020606154604051908152f35b346102dc5760206003193601126102dc5761059b6120b1565b6001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576001600160a01b0316600052607e6020526020604060002054604051908152f35b7f8b2731aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206001600160a01b0360015416604051908152f35b346102dc5760006003193601126102dc576020603c54604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102dc5760006003193601126102dc576020603e54604051908152f35b346102dc5760206003193601126102dc576040516106d181612222565b600081526000602082015260006040820152600060608201526080604051916106f98361223e565b60008352600060208401520152600435600052605f60205260406000206001600160a01b036003820154169081156107d2576001600160a01b036040519261074084612222565b82548452600183015492602085019384526107706004600283015492604088019384526060880194855201613882565b936080860194855260405195518652516020860152516040850152511660608301525190815160068110156107bc5760c092602091608084015201516107b5816121b7565b60a0820152f35b634e487b7160e01b600052602160045260246000fd5b7fd9e125150000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5761080a3661212c565b906001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576004811015610ad15780600052603960205261085c60406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000003908162278d00029162278d008304036109e05710610aa75761089d61416c565b8060005260056020526040600020548211600014610a8a578060005260056020526108cd60406000205483612573565b8160005260066020526040600020548311908115610a60575b8115610a36575b50610a0c578060005260396020524260406000205560405161090e81612206565b8181528260208201524260408201526036549060048210156109f657600361094f920260250190604060029180518455602081015160018501550151910155565b603654906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086036558160005260056020526040600020546040519260808452600a60808501527f50656e616c74794665650000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b7f573a59ba0000000000000000000000000000000000000000000000000000000060005260046000fd5b90507f000000000000000000000000000000000000000000000000000000000000000110836108ed565b7f0000000000000000000000000000000000000000000000000000000000000000841091506108e6565b806000526005602052610aa282604060002054612573565b6108cd565b7fc2c1e97b0000000000000000000000000000000000000000000000000000000060005260046000fd5b7ffc87de8c0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000648152f35b346102dc57610b443661212c565b906001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576006811015610ad157806000526037602052610b9660406000205442612573565b7f0000000000000000000000000000000000000000000000000000000000000003908162278d00029162278d008304036109e05710610aa757610bd7613f96565b8060005260036020526040600020548211600014610d7f57806000526003602052610c0760406000205483612573565b7f00000000000000000000000000000000000000000000000000000000000003e88311908115610d55575b8115610d2b575b50610a0c5780600052603760205242604060002055604051610c5a81612206565b8181528260208201524260408201526032549060068210156109f6576003610c9b920260070190604060029180518455602081015160018501550151910155565b60325490600182018092116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f092600660c093066032558160005260036020526040600020546040519260808452600960808501527f54696572426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f00000000000000000000000000000000000000000000000000000000000000011083610c39565b7f000000000000000000000000000000000000000000000000000000000000006484109150610c32565b806000526003602052610d9782604060002054612573565b610c07565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc576020606054604051908152f35b346102dc5760206003193601126102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576104e4600435613792565b346102dc5760206003193601126102dc576001600160a01b03610e626120b1565b610e6a613f82565b1680156103ee576001600160a01b0360005416811461039a576001600160a01b03603b54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603a541617603a55337f6d0f78b3a4ae2472e08ee885ed47bfb1ace996c12d6ff019537c99821244de8d600080a3005b7fd06f5c380000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576104e4600435610f3481603e546122b5565b603e55604051818152600160208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b346102dc5760206003193601126102dc5760043560005260046020526020604060002054604051908152f35b346102dc5760206003193601126102dc576001600160a01b03610fe86120b1565b610ff0613f82565b1680156103ee576001600160a01b036000541681146103c4576001600160a01b03603a54168114610ee757807fffffffffffffffffffffffff0000000000000000000000000000000000000000603b541617603b55337f786bbb915c47f5cb032b19e70cd81f673f1ee5c1ac711db6681470264d521125600080a3005b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc5733600052607e6020526020600160406000200154607d541115604051908152f35b346102dc5760406003193601126102dc576110f36120b1565b6024356001600160a01b03603a5416331415806111bb575b806111a6575b6105105761111e816140d8565b603d54918282116104e657611136826104e494612573565b603d55604051828152600260208201526001600160a01b038216907f30e7b5ec2178d118be8da7ccd5e8d038d5310e90eca9b987da95b9577905c72d60403392a36001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b506001600160a01b0360005416331415611111565b506001600160a01b03603b541633141561110b565b346102dc5760206003193601126102dc5760206111ee6004356135de565b6040519015158152f35b346102dc5760006003193601126102dc5760206001600160a01b0360005416604051908152f35b346102dc5760006003193601126102dc5760e061125b7f5a4e445374616b696e670000000000000000000000000000000000000000000a61453e565b61130d6112877f31000000000000000000000000000000000000000000000000000000000000016146a2565b916112ec6020936112de6040519361129f878661225a565b6000855260003681376040519788977f0f00000000000000000000000000000000000000000000000000000000000000895288015260e0870190612142565b908582036040870152612142565b90466060850152306080850152600060a085015283820360c0850152612183565b0390f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc5760206001600160a01b03603b5416604051908152f35b346102dc5760006003193601126102dc57600154336001600160a01b03821603611419577fffffffffffffffffffffffff000000000000000000000000000000000000000016600155600054337fffffffffffffffffffffffff00000000000000000000000000000000000000008216176000556001600160a01b033391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f118cdaa7000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000648152f35b346102dc5760006003193601126102dc5761149b613f82565b7f23dbc96b0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576104e46133ee565b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000038152f35b346102dc5760406003193601126102dc5760243567ffffffffffffffff81116102dc5761154a9036906004016120c7565b6001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943163314611586576104e4916004356131f5565b7f163156450000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760006003193601126102dc576020608054604051908152f35b346102dc576115dc366120f5565b906001600160a01b039392937f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943163314611586576020936111ee93613058565b346102dc5760206003193601126102dc5760043560005260066020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043560005260056020526020604060002054604051908152f35b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc5736602460608402830101116102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec5760209160246111ee9201612c92565b346102dc576117133661212c565b906001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec576004811015610ad15780600052603860205261176560406000205442612573565b7f00000000000000000000000000000000000000000000000000000000000000039062278d0082029180830462278d0014901517156109e05710610aa7576117ab613f96565b8060005260046020526040600020548211600014611954578060005260046020526117db60406000205483612573565b7f00000000000000000000000000000000000000000000000000000000000003e8831190811561192a575b8115611900575b50610a0c578060005260386020524260406000205560405161182e81612206565b8181528260208201524260408201526034549060048210156109f657600361186f920260190190604060029180518455602081015160018501550151910155565b603454906001820182116109e0577ff87a7e5306f4bc9e20b9719b4777a41e3b1a05a173f9c24fef483ef79ccbf3f0926004600160c094086034558160005260046020526040600020546040519260808452600960808501527f506c616e426f6f7374000000000000000000000000000000000000000000000060a0850152602084015260408301526060820152a1005b90507f0000000000000000000000000000000000000000000000000000000000000001108361180d565b7f000000000000000000000000000000000000000000000000000000000000006484109150611806565b80600052600460205261196c82604060002054612573565b6117db565b346102dc5760006003193601126102dc5760206001600160a01b03603a5416604051908152f35b346102dc5760206003193601126102dc576004356119b4613f82565b806040556040519081527f075e0c6585fd90d164d2f7f5d5399e97900ed892807821f47be4d64c9eb5238160203392a2005b346102dc5760006003193601126102dc5760206040516001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d168152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000000018152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000003e88152f35b346102dc5760206003193601126102dc576004356001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec57606154611b1c576020817fe056d7ccce0301fe61ac060fe377a3c89b179cd09fa62ef48618d5791a78d64892607f55604051908152a1005b7fac5cadc30000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760206003193601126102dc576001600160a01b03611b676120b1565b16600052603f6020526060604060002060405190611b84826121d4565b80549081835260018101549260208101938452600360028301549260408301938452015494859101528192600094804211611c04575b509360209481611bcf575b8585604051908152f35b51611bf39450611bed92919080821015611bfc5750905b51906121c1565b906122b5565b82808080611bc5565b905090611be6565b611c12620151809142612573565b0460018101809111611c275794506020611bba565b602486634e487b7160e01b81526011600452fd5b346102dc5760006003193601126102dc576020603d54604051908152f35b346102dc5760006003193601126102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec57606254600214611cd4576002606255426063557faeb375a1784645db3075ba87abdf2952280755ad1967e93aea416f217386a1bb600080a1005b7ff62324030000000000000000000000000000000000000000000000000000000060005260046000fd5b346102dc5760406003193601126102dc5760043560068110156102dc576024359060048210156102dc57611d31826121b7565b6006820291808304600614901517156109e057611d4d916122b5565b60188110156109f65760209060640154604051908152f35b346102dc57611d73366120f5565b906001600160a01b039392937f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943163314611586576020936111ee936127ed565b346102dc5760206003193601126102dc576104e4600435611dd681603c546122b5565b603c55604051818152600060208201527f3550a4e047f126bc2308a83f3eca4dfc95a7040197cae9f050f4cafe2539fd1960403392a230336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b346102dc5760206003193601126102dc5760043567ffffffffffffffff81116102dc57366023820112156102dc5780600401359067ffffffffffffffff82116102dc573660248360061b830101116102dc576001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec5760209160246111ee9201612580565b346102dc5760206003193601126102dc5760043560005260036020526020604060002054604051908152f35b346102dc5760006003193601126102dc5760206040517f00000000000000000000000000000000000000000000000000000000000003e88152f35b346102dc5760006003193601126102dc5760206040516001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943168152f35b346102dc5760006003193601126102dc576020604054604051908152f35b346102dc5760806003193601126102dc576104e4611fb36120b1565b60643590604435906024359061231f565b346102dc5760206003193601126102dc576004356001600160a01b037f000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d81288599431633036105ec5760207ff1649f58e4104d40851477a70db43e4ed6979ae52986533c3bc43abb6642dcfa916120346138c5565b80608055604051908152a1005b346102dc5760006003193601126102dc57612069336000526044602052604060002054151590565b156120875733600052607e6020526020604060002054604051908152f35b7f72a7bddc0000000000000000000000000000000000000000000000000000000060005260046000fd5b600435906001600160a01b03821682036102dc57565b9181601f840112156102dc5782359167ffffffffffffffff83116102dc57602083818601950101116102dc57565b60606003198201126102dc5760043591602435916044359067ffffffffffffffff82116102dc57612128916004016120c7565b9091565b60031960409101126102dc576004359060243590565b919082519283825260005b84811061216e575050601f19601f8460006020809697860101520116010190565b8060208092840101518282860101520161214d565b906020808351928381520192019060005b8181106121a15750505090565b8251845260209384019390920191600101612194565b600411156107bc57565b818102929181159184041417156109e057565b6080810190811067ffffffffffffffff8211176121f057604052565b634e487b7160e01b600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176121f057604052565b60a0810190811067ffffffffffffffff8211176121f057604052565b6040810190811067ffffffffffffffff8211176121f057604052565b90601f601f19910116810190811067ffffffffffffffff8211176121f057604052565b60405191906000835b6018821061229f5750505061229d6103008361225a565b565b6001602081928554815201930191019091612286565b919082018092116109e057565b9060188110156109f65760051b0190565b60188210156109f6570190600090565b80548210156109f65760005260206000209060011b0190600090565b8115612309570490565b634e487b7160e01b600052601260045260246000fd5b6001600160a01b0390939192936123346138c5565b169261234d846000526044602052604060002054151590565b156125005783600052607e602052600160406000200154607d54111561250057836000526045602052612383604060002061227d565b906000936006810290808204600614901517156124ec576123ae916123a7916122b5565b80926122c2565b51806123fa575b50505081600052607e6020526123d160406000209182546122b5565b90557fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b848452607e60205261242361241583600260408820016122d3565b90549060031b1c93846122b5565b92828552607c60205260408520548410156124d8575b905b8382106124825750506124616124789185600052607e60205260026040600020016122d3565b81939154906000199060031b92831b921b19161790565b90553880806123b5565b90936124d060019184600052607c602052611bed6124b1846124a88a60406000206122e3565b500154866121c1565b86600052607c6020526124c88960406000206122e3565b5054906122ff565b94019061243b565b828552607c60205260408520549350612439565b602485634e487b7160e01b81526011600452fd5b50505050565b91908110156109f65760061b0190565b67ffffffffffffffff81116121f05760051b60200190565b9061253882612516565b612545604051918261225a565b828152601f196125558294612516565b0190602036910137565b80518210156109f65760209160051b010190565b919082039182116109e057565b919080156127645760005b81811061271e575061259b6133ee565b6125b2336000526044602052604060002054151590565b80612702575b6126d457916125c68361252e565b600093849285915b808310612689575050506126407f6662f35bbdebb39ebf472c94252ecf0dd0c55fc8d0a922e0595b12c53d643937918561261761260f6126849798876122b5565b606154612573565b60615561262681603d546122b5565b603d55815190604051938493608085526080850190612183565b91602084015285604084015260608301520390a1336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b600190565b909193956126cb6001916126c56126a1888688612506565b916126b26020843594013584613c29565b9190936126bf8b8b61255f565b526122b5565b986122b5565b940191906125ce565b509050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106125b8565b602061272b828487612506565b01351561273a5760010161258b565b7f115e63aa0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f930363960000000000000000000000000000000000000000000000000000000060005260046000fd5b67ffffffffffffffff81116121f057601f01601f191660200190565b9291926127b68261278e565b916127c4604051938461225a565b8294818452818301116102dc578281602093846000960137010152565b60068210156107bc5752565b93929190916000918315612c5a5761287f916128706128769261286889604051612860816128528c60208301957f82186402b6a35725297ac7691ca0a6e5be47eef3d200a5bc491a4e89d99cadde876040919493926060820195825260208201520152565b03601f19810183528261225a565b519020613f07565b9236916127aa565b90614435565b90929192614471565b6001600160a01b0333911603612c32576128976133ee565b6128ae336000526044602052604060002054151590565b80612c18575b612bec576128c1846121b7565b604051936128ce8561223e565b81855260208501946128df826121b7565b81865251946006861015612bd857516128f7816121b7565b612900816121b7565b6040519561290d87612206565b8487526020870181905260408701918252906006821015612bc45751612932816121b7565b612948604051926129428461223e565b836127e1565b612951816121b7565b60208201526060549250855195600460405161296c81612222565b858152602081019889526001600160a01b03604082014281526060830190338252608084019b878d5289600052605f60205260406000209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff000000000000000000000000000000000000000082541617905501965196875160068110156107bc576000987fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612a32816121b7565b612a3b816121b7565b60081b16931691161717905580513388526046602052612a60604089209182546122b5565b90558051612a6d8361421e565b6018811015612bb05791612aac91612a8d612ad3946064019182546122b5565b905551923389526045602052612aa660408a209161421e565b906122d3565b612abf8294925492838360031b1c6122b5565b91906000199060031b92831b921b19161790565b9055612ade336148f1565b612b83575b612aef836061546122b5565b60615560605460018101809111611c2757612684949550606055612b12816121b7565b604051918252826020830152612b27816121b7565b60408201527f8629b9bbe2698bb9733aab9759f36ec9390475da11e15309b08e8755ea5862fd60603392a230336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b338552607e602052612b9a60026040872001613f48565b338552607e602052426001604087200155612ae3565b602489634e487b7160e01b81526032600452fd5b602484634e487b7160e01b81526021600452fd5b602483634e487b7160e01b81526021600452fd5b91925050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b08280a290565b50607d54338252607e6020526001604083200154106128b4565b807f710651b70000000000000000000000000000000000000000000000000000000060049252fd5b6004837f905091fe000000000000000000000000000000000000000000000000000000008152fd5b91908110156109f6576060020190565b9190801561302e5760005b818110612feb5750612cad6133ee565b612cc4336000526044602052604060002054151590565b80612fcf575b6126d457600090612cda8161252e565b93825b828110612d82575050612d42612684939482612d1c7f254fa5b38abff01b7e340b6d943b8e9c17ff5a3f7fe4a447dc3e92ffb3705a0e946060546122b5565b606055612d2b856061546122b5565b606155604051928392606084526060840190612183565b9060208301528460408301520390a130336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613ead565b612d8d818484612c82565b93612d9a853580926122b5565b946060813603126102dc5760405191612db283612206565b82526020820190602081013582526040808401910135815260009151906006821015612bd85751612de2816121b7565b612df2604051926129428461223e565b612dfb816121b7565b6020820152612e0c846060546122b5565b9280516004604051612e1d81612222565b868152602081019283526001600160a01b0360408201428152606083019033825260808401958887528a8a52605f60205260408a209451855551600185015551600284015551166001600160a01b036003830191167fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790550190518051906006821015612fbb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000060ff61ff0060208654940151612ede816121b7565b612ee7816121b7565b60081b16931691161717905580513384526046602052612f0c604085209182546122b5565b90558051612f198361421e565b6018811015612fa75760019695949392612f5892612f3f612aac936064019182546122b5565b905551923385526045602052612aa6604086209161421e565b9055612f63336148f1565b612f7a575b50612f73828961255f565b5201612cdd565b338152607e602052612f9160026040832001613f48565b338152607e602052836040429220015538612f68565b602485634e487b7160e01b81526032600452fd5b602486634e487b7160e01b81526021600452fd5b50607d5433600052607e60205260016040600020015410612cca565b612ff6818386612c82565b351561300457600101612c9d565b7f905091fe0000000000000000000000000000000000000000000000000000000060005260046000fd5b7f80ef666a0000000000000000000000000000000000000000000000000000000060005260046000fd5b91929092831561273a576130b991612870612876926128686040516020810190612860816128528c8c7fbda4078728c0ae5140c1cd85bf1abff4d86904b62dc8d2823ae88a05b34e2e37876040919493926060820195825260208201520152565b6001600160a01b03339116036131cb576130d16133ee565b6130e8336000526044602052604060002054151590565b806131af575b613182576130ff6126849282613c29565b909161310e61260f83856122b5565b60615561311d82603d546122b5565b603d556040519183835260208301527f277a1a847b4895ea9853b0f19969c04b8d17cb55b9ad4504179ede80fbf0f7af60403393a3336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b5050337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2600090565b50607d5433600052607e602052600160406000200154106130ee565b7f710651b70000000000000000000000000000000000000000000000000000000060005260046000fd5b919082156133c45761324b916128706128769261286860405160208101907f66a29e1886f085dbd8ec4fc1f6b13da644ad0efad4477114339037860a802dfe82528860408201526040815261286060608261225a565b6001600160a01b03339116036131cb576132636133ee565b61327a336000526044602052604060002054151590565b806133a8575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206132b2828254612573565b9055603c549080821061332b576132cc8161229d93612573565b603c556040518181527fecf9667816c0bdb34ff25e5437430e488cebc3841c3a2c90e3927e07afcd78b460203392a2336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b7f4aa330410000000000000000000000000000000000000000000000000000000060005260046000fd5b7fa689c4650000000000000000000000000000000000000000000000000000000060005260046000fd5b50337fb5a3d50918e2c741c38b5cec430455a04abb60d8cba398e1993353b89ac367b0600080a2565b50607d5433600052607e60205260016040600020015410613280565b7f8f9ba5d50000000000000000000000000000000000000000000000000000000060005260046000fd5b6133f66138c5565b61340d336000526044602052604060002054151590565b1561229d5733600052607e602052600160406000200154607d54111561229d57336000526045602052613443604060002061227d565b60009060029060005b600481106134b95750506002146134a1575b33600052607e60205261347760406000209182546122b5565b9055337fc8a9648dede4010ef726812915dc6433e01c678de73c6626b5fad1400f7a1f76600080a2565b33600052607e6020524260016040600020015561345e565b60005b600681106134cd575060010161344c565b60068202828104600614831517156109e057816134e9916122b5565b6134f381856122c2565b519081156135d45733600052607e6020526135158160026040600020016122d3565b90549060031b1c916102dc83018084116109e0576000838152607c602052604090205481106135ca575081600052607c602052604060002054925b905b8382106135855750509061357c6124616001949333600052607e60205260026040600020016122d3565b90555b016134bc565b90976135c2600191611bed6135ab846124a88e89600052607c60205260406000206122e3565b86600052607c6020526124c88d60406000206122e3565b980190613552565b6001975092613550565b505060019061357f565b33600052603f60205260406000206040516135f8816121d4565b815481526060600183015491602081019283526003600285015494604083019586520154918291015260009080421161375c575b5090816136cd575b50505033600052603f60205280604060002054106136c7576126849033600052603f6020526040600020613669828254612573565b90556040518181527fcf93002ecae3f252116b013a1ae8ec072919c19ab134261c065cc94d5b0b3e5860203392a2336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b50600090565b518082101561375557505b8062015180029162015180830482036109e0576137329233600052603f60205261370b60036040600020019182546122b5565b905533600052603f6020526001604060002001613729838254612573565b905551906121c1565b33600052603f60205261374b60406000209182546122b5565b9055388080613634565b90506136d8565b61376a620151809142612573565b04906001820180921161377e57503861362c565b80634e487b7160e01b602492526011600452fd5b80156133c4576137a06133ee565b6137b7336000526044602052604060002054151590565b80613866575b61337f5733600052607e60205260406000205481116133555733600052607e60205260406000206137ef828254612573565b9055603c549080821061332b576138098161229d93612573565b603c557f916496170aac2ab5c6b8c47b015f9a422bab4f77114f70a76c7289a2e7312eb46020604051838152a1336001600160a01b037f0000000000000000000000002d8ea194902bc55431420bd26be92b0782dce91d16613e58565b50607d5433600052607e602052600160406000200154106137bd565b9060405161388f8161223e565b602060ff8294546138a2828216856127e1565b60081c16916138b0836121b7565b0152565b60ff1660ff81146109e05760010190565b60625460011461229d57606354421061229d576080541561229d576138e8613f96565b620151806138f860635442612573565b04600101806001116109e057600060189261391284612516565b90613920604051928361225a565b848252601f1961392f86612516565b0136602084013760405191606484845b888210613c12575050506139556103008461225a565b835b60ff8116946004861015613a365795926000906006870298878a0460061488151715985b60ff84166006811015613a19578a6109e057613997818d6122b5565b908a8a6139a484826122c2565b5115613a0b57926127106139f56126bf946139e0613a0598956139ca866139ff9a6122c2565b51906000526004602052604060002054906121c1565b906000526003602052604060002054906121c1565b049283918b61255f565b936138b4565b9261397b565b5050505092613a05906138b4565b50959750985092965050613a2c906138b4565b9490919394613957565b919592969450508015613be0576080549160005b60ff81166004811015613b8257801560068281029283041417159860005b60ff81166006811015613b6f578b6109e0578787613a91613a8a8e94886122b5565b809461255f565b518015613b625790613ac6613acb92613ac18b60405196613ab18861223e565b60008852600060208901526121c1565b6121c1565b6122ff565b9060208101918252613add838c6122c2565b51815282600052607c60205260406000208054680100000000000000008110156121f057613b10916001820181556122e3565b929092613b4c5760019151835551910155898110156109f6576047019081549160001983146109e0576001613b47930190556138b4565b613a68565b634e487b7160e01b600052600060045260246000fd5b50505050613b47906138b4565b5050985050613b7d906138b4565b613a4a565b505050505050505042607d55613b9d62015180420642612573565b6201518081018091116109e057607f54613bb6916122b5565b6063557f040bde66f3369b7fc094e7401b5de26a5be6900e0fb645651d59eb57b8620dbc600080a1565b5050505050613bf462015180420642612573565b6201518081018091116109e057607f54613c0d916122b5565b606355565b82548152600192830192919091019060200161393f565b919091613c3461416c565b80600052605f6020526040600020906001600160a01b0360038301541680156107d25784600184015410613e2e573303613e045760009380926004600282015491019060ff825460081c1690613c89826121b7565b816000526002602052604060002054620151808102908082046201518014901517156109e057613cb99042612573565b10613dc9575b503360005260466020526040600020613cd9838254612573565b9055613cec613ce782613882565b61421e565b60188110156109f657613d3b91613d2791606401613d0b858254612573565b9055336000526045602052612aa6613ce7604060002092613882565b8192915490612abf85838360031b1c612573565b905533600052604660205260406000205415613db7575b81600052605f602052613d6e6001604060002001918254612573565b905580600052605f60205260016040600020015415613d8c57509190565b600052605f602052600060046040822082815582600182015582600282015582600382015501559190565b613dc03361478c565b613d5257600080fd5b9195509250613dd7816121b7565b6000526005602052612710613df1604060002054866121c1565b0493613dfd8582612573565b9238613cbf565b7f387224d40000000000000000000000000000000000000000000000000000000060005260046000fd5b7fe0a6f7200000000000000000000000000000000000000000000000000000000060005260046000fd5b61229d926001600160a01b03604051937fa9059cbb000000000000000000000000000000000000000000000000000000006020860152166024840152604483015260448252613ea860648361225a565b61425f565b9091926001600160a01b0361229d9481604051957f23b872dd000000000000000000000000000000000000000000000000000000006020880152166024860152166044840152606483015260648252613ea860848361225a565b604290613f12614319565b90604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b60478114613f7f57604790604754906000915b60188310613f695750505050565b6001809194019283549481840155019192613f5b565b50565b6001600160a01b0360005416330361141957565b6031547f0000000000000000000000000000000000000000000000000000000000000001801562278d0080830292830414171592915b60325481146140d05760068110156109f6576003810284613ff1600983015442612573565b906109e0578310156140425760009060088101549150600701546000526003602052604060002055603154600181018091116109e05760069006603155600181018091116109e05760069006613fcc565b505091905b6033545b60345481146140ca5760048110156109f6576003810283614070601b83015442612573565b906109e0578510156140c357600090601a81015491506019015460005260046020526040600020556033546001810181116109e0576001600491086033556001810181116109e05760016004910861404b565b5050915050565b50915050565b509190614047565b801561414257604054106140e857565b6001600160a01b03600054163314158061412d575b61410357565b7f1c2a55280000000000000000000000000000000000000000000000000000000060005260046000fd5b506001600160a01b03603b54163314156140fd565b7f11184a7b0000000000000000000000000000000000000000000000000000000060005260046000fd5b6035547f00000000000000000000000000000000000000000000000000000000000000019162278d0083029280840462278d001490151715915b60365481146140ca5760048110156109f65760038102836141cb602783015442612573565b906109e0578510156140c357600090602681015491506025015460005260056020526040600020556035546001810181116109e0576001600491086035556001810181116109e0576001600491086141a6565b60208101519061422d826121b7565b614236826121b7565b6006820291808304600614901517156109e0575160068110156107bc5761425c916122b5565b90565b6000806001600160a01b036142aa93169360208151910182865af13d15614311573d9061428b8261278e565b91614299604051938461225a565b82523d6000602084013e5b8361494b565b80519081151591826142ed575b50506142c05750565b7f5274afe70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b81925090602091810103126102dc57602001518015908115036102dc5738806142b7565b6060906142a4565b6001600160a01b037f000000000000000000000000cb3d2be5386a13c87944f9384214ead9a6dba7921630148061440c575b15614374577f1f1088bc52d56d74f7207acfdfab86e466cfd7b3211e8c1abcaaf8c3acad074c90565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f9dfb93b476d65e79d590230e42fa549a24db10aa55195af52886fa544a966ad560408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a0815261440660c08261225a565b51902090565b507f0000000000000000000000000000000000000000000000000000000000000001461461434b565b81519190604183036144665761445f92506020820151906060604084015193015160001a9061485c565b9192909190565b505060009160029190565b61447a816121b7565b80614483575050565b61448c816121b7565b600181036144be577ff645eedf0000000000000000000000000000000000000000000000000000000060005260046000fd5b6144c7816121b7565b600281036144fd57507ffce698f70000000000000000000000000000000000000000000000000000000060005260045260246000fd5b600390614509816121b7565b146145115750565b7fd78bce0c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60ff81146145a25760ff811690601f8211614578576040805192614562828561225a565b60208452601f1960208501920136833783525290565b7fb3512b0c0000000000000000000000000000000000000000000000000000000060005260046000fd5b506040516000604154908160011c91600181168015614698575b60208410811461468457838552849291811561464757506001146145e7575b61425c9250038261225a565b506041600090815290917f7c9785e8241615bc80415d89775984a1337d15dc1bf4ce50f41988b2a2b336a75b81831061462b57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614613565b6020925061425c9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016145db565b602483634e487b7160e01b81526022600452fd5b92607f16926145bc565b60ff81146146c65760ff811690601f8211614578576040805192614562828561225a565b506040516000604254908160011c9160018116801561476a575b602084108114614684578385528492918115614647575060011461470a5761425c9250038261225a565b506042600090815290917f38dfe4635b27babeca8be38d3b448cb5161a639b899a14825ba9c8d7892eb8c35b81831061474e57505090602061425c928201016145db565b6020919350806001915483858801015201910190918392614736565b92607f16926146e0565b80548210156109f65760005260206000200190600090565b60008181526044602052604090205480156148555760001981018181116109e0576043549060001982019182116109e05781810361481b575b505050604354801561480557600019016147e0816043614774565b60001982549160031b1b19169055604355600052604460205260006040812055600190565b634e487b7160e01b600052603160045260246000fd5b61483d61482c612461936043614774565b90549060031b1c9283926043614774565b905560005260446020526040600020553880806147c5565b5050600090565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116148e5579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa156148d9576000516001600160a01b038116156148cd5790600090600090565b50600090600190600090565b6040513d6000823e3d90fd5b50505060009160039190565b806000526044602052604060002054156000146136c757604354680100000000000000008110156121f0576149326124618260018594016043556043614774565b9055604354906000526044602052604060002055600190565b9061498a575080511561496057805190602001fd5b7f1425ea420000000000000000000000000000000000000000000000000000000060005260046000fd5b815115806149d2575b61499b575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b50803b1561499356fea264697066735822122029bc36c2c231c119492b3ac9d5378cd3e143f3f82f63c8339cc7048bd177894764736f6c634300081a0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d812885994300000000000000000000000036ac695293d35bd90586b5353471c2004407ef6e00000000000000000000000041c31f690873ec5a642dd374e2f2dd1528d057300000000000000000000000000000000000000000000007b864634d3cc768000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6d34ae9275e1a900000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000036fdb8800968fb8b38d53ebce3c86f909c79637200000000000000000000000000000000000000000031ef6e017e6033e9a00000000000000000000000000000000000000000000000000000000000000000016800000000000000000000000000000000000000000000000000000000013c680000000000000000000000000000000000000000000006cf2f721f56aca1600000000000000000000000000000552002ec9504f3931019d63a8d992c98fcdf1eb70000000000000000000000000000000000000000003b41ea57328adb16b00000000000000000000000000000000000000000000000000000000000000000012c0000000000000000000000000000000000000000000000000000000000ed4e0000000000000000000000000000000000000000000009a58bb5318a10ffd00000000000000000000000000000719d74be74980f94b2b9ee670f94f540661f9f6600000000000000000000000000000000000000000004a1de0780a6934f100000000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000076a700000000000000000000000000000000000000000000012870bb26f3ec17f000000000000000000000000000004c5e31774b09cf489c482a472bb78ae8c024c70100000000000000000000000000000000000000000003e41c59d913dabc00000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000bdbc41e0348b300000000000000000000000000000003ce0935e2f28339d6f9f1068356a90bf6d3173e3000000000000000000000000000000000000000000045348a87272a44a20000000000000000000000000000000000000000000000000000000000000000000d2000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000000e2cb06bdfece5f6000000000000000000000000000009ff1299f5abea9ff1789c86e1c972ac1b3a8d38d0000000000000000000000000000000000000000000445f16bd0aef280c0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000004f1a0000000000000000000000000000000000000000000000f022435fc28028c00000000000000000000000000000ca4bde17f2a29fd041665cee2d206bc5c2ae507f00000000000000000000000000000000000000000004389a2f2eeb40b760000000000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000fd7980018631f2200000000000000000000000000000ae0a2dbdfdc0b9d1132b52796d5fcc9a19bd5dc300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004a1d89bb94865ec00000000000000000000000000000053423085f352a12f0b4c51e00c512e130ae02b3800000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000076a70000000000000000000000000000000000000000000004a1d89bb94865ec000000000000000000000000000000af3bf534d2b026b9c72cb6fb30a5f7f879a6f4f1000000000000000000000000000000000000000000a7eaa0281559f34020000000000000000000000000000000000000000000000000000000000000000007080000000000000000000000000000000000000000000000000000000003b5380000000000000000000000000000000000000000000000004be4e7267b6ae00000000000000000000000000000344b16fe768582f69205897d240e2971b3ac0c09000000000000000000000000000000000000000000250ec5f358893873d0000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000943b021e44ac2c4300000000000000000000000000000c5d2d690d683c6f7bb654e91fed293caec2a26b50000000000000000000000000000000000000000001851a42ee173aa2940000000000000000000000000000000000000000000000000000000000000000002d0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000004a1e59e6490d2d9c0000000000000000000000000000018bf7f1cf41f840a7cb21618d5d1c1af7ca3e9080000000000000000000000000000000000000000002e524435ac3e59a8a0000000000000000000000000000000000000000000000000000000000000000004380000000000000000000000000000000000000000000000000000000001da9c00000000000000000000000000000000000000000000000031df9095a18f600000000000000000000000000000fdf5b0d1cd40223ef91027ce014cb4a31ac4ecf200000000000000000000000000000000000000000029b09d79838b954c00000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000001bccb3fbc0c0014000000

-----Decoded View---------------
Arg [0] : _zndPlatform (address): 0xAe4F32dFa85B9e8c424A9604E7bF1D8128859943
Arg [1] : _discretionaryWithdrawalAccount (address): 0x36Ac695293D35BD90586B5353471c2004407eF6E
Arg [2] : _overLimitWithdrawalAccount (address): 0x41c31F690873Ec5A642Dd374e2F2Dd1528d05730
Arg [3] : _initialDailyReward (uint256): 36458000000000000000000
Arg [4] : _initialDiscretionaryPoolBalance (uint256): 0
Arg [5] : _initialRewardsPoolBalance (uint256): 52499520000000000000000000
Arg [6] : _vestingPayload (tuple[]):
Arg [1] : recipient (address): 0x36fDB8800968fB8b38d53ebce3c86f909c796372
Arg [2] : quantityToUnlockOverTime (uint256): 60368040000000000000000000
Arg [3] : numberOfDays (uint256): 360
Arg [4] : cliffPeriodEnd (uint256): 20736000
Arg [5] : initialUnlock (uint256): 8231960000000000000000000

Arg [1] : recipient (address): 0x552002EC9504f3931019d63A8D992C98fcDf1Eb7
Arg [2] : quantityToUnlockOverTime (uint256): 71637900000000000000000000
Arg [3] : numberOfDays (uint256): 300
Arg [4] : cliffPeriodEnd (uint256): 15552000
Arg [5] : initialUnlock (uint256): 11662100000000000000000000

Arg [1] : recipient (address): 0x719d74bE74980F94b2B9ee670f94f540661F9F66
Arg [2] : quantityToUnlockOverTime (uint256): 5600100000000000000000000
Arg [3] : numberOfDays (uint256): 300
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 1399900000000000000000000

Arg [1] : recipient (address): 0x4c5e31774b09cF489C482A472BB78Ae8C024c701
Arg [2] : quantityToUnlockOverTime (uint256): 4704000000000000000000000
Arg [3] : numberOfDays (uint256): 210
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 896000000000000000000000

Arg [1] : recipient (address): 0x3Ce0935E2f28339d6f9f1068356a90Bf6d3173E3
Arg [2] : quantityToUnlockOverTime (uint256): 5229000000000000000000000
Arg [3] : numberOfDays (uint256): 210
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 1071000000000000000000000

Arg [1] : recipient (address): 0x9Ff1299f5Abea9ff1789c86E1C972Ac1b3A8D38D
Arg [2] : quantityToUnlockOverTime (uint256): 5166000000000000000000000
Arg [3] : numberOfDays (uint256): 180
Arg [4] : cliffPeriodEnd (uint256): 5184000
Arg [5] : initialUnlock (uint256): 1134000000000000000000000

Arg [1] : recipient (address): 0xCa4BdE17f2a29fd041665cee2D206Bc5c2Ae507F
Arg [2] : quantityToUnlockOverTime (uint256): 5103000000000000000000000
Arg [3] : numberOfDays (uint256): 150
Arg [4] : cliffPeriodEnd (uint256): 2592000
Arg [5] : initialUnlock (uint256): 1197000000000000000000000

Arg [1] : recipient (address): 0xaE0A2DbDFDc0B9d1132B52796d5FCC9a19bd5dc3
Arg [2] : quantityToUnlockOverTime (uint256): 0
Arg [3] : numberOfDays (uint256): 0
Arg [4] : cliffPeriodEnd (uint256): 0
Arg [5] : initialUnlock (uint256): 5600000000000000000000000

Arg [1] : recipient (address): 0x53423085F352A12f0B4c51E00C512e130Ae02b38
Arg [2] : quantityToUnlockOverTime (uint256): 50400000000000000000000000
Arg [3] : numberOfDays (uint256): 720
Arg [4] : cliffPeriodEnd (uint256): 7776000
Arg [5] : initialUnlock (uint256): 5600000000000000000000000

Arg [1] : recipient (address): 0xAF3BF534D2b026b9c72Cb6fB30a5F7F879A6F4f1
Arg [2] : quantityToUnlockOverTime (uint256): 202998600000000000000000000
Arg [3] : numberOfDays (uint256): 1800
Arg [4] : cliffPeriodEnd (uint256): 62208000
Arg [5] : initialUnlock (uint256): 1400000000000000000000

Arg [1] : recipient (address): 0x344b16FE768582f69205897d240e2971b3AC0c09
Arg [2] : quantityToUnlockOverTime (uint256): 44800020000000000000000000
Arg [3] : numberOfDays (uint256): 180
Arg [4] : cliffPeriodEnd (uint256): 86400
Arg [5] : initialUnlock (uint256): 11199980000000000000000000

Arg [1] : recipient (address): 0xC5D2D690D683C6F7bB654e91Fed293cAec2A26B5
Arg [2] : quantityToUnlockOverTime (uint256): 29399760000000000000000000
Arg [3] : numberOfDays (uint256): 720
Arg [4] : cliffPeriodEnd (uint256): 86400
Arg [5] : initialUnlock (uint256): 5600240000000000000000000

Arg [1] : recipient (address): 0x18BF7f1CF41f840A7cB21618D5D1C1AF7CA3E908
Arg [2] : quantityToUnlockOverTime (uint256): 55999080000000000000000000
Arg [3] : numberOfDays (uint256): 1080
Arg [4] : cliffPeriodEnd (uint256): 31104000
Arg [5] : initialUnlock (uint256): 920000000000000000000

Arg [1] : recipient (address): 0xfDf5b0d1cd40223ef91027cE014Cb4A31AC4ECf2
Arg [2] : quantityToUnlockOverTime (uint256): 50400000000000000000000000
Arg [3] : numberOfDays (uint256): 1440
Arg [4] : cliffPeriodEnd (uint256): 86400
Arg [5] : initialUnlock (uint256): 2100480000000000000000000


-----Encoded View---------------
78 Constructor Arguments found :
Arg [0] : 000000000000000000000000ae4f32dfa85b9e8c424a9604e7bf1d8128859943
Arg [1] : 00000000000000000000000036ac695293d35bd90586b5353471c2004407ef6e
Arg [2] : 00000000000000000000000041c31f690873ec5a642dd374e2f2dd1528d05730
Arg [3] : 0000000000000000000000000000000000000000000007b864634d3cc7680000
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [5] : 0000000000000000000000000000000000000000002b6d34ae9275e1a9000000
Arg [6] : 00000000000000000000000000000000000000000000000000000000000000e0
Arg [7] : 000000000000000000000000000000000000000000000000000000000000000e
Arg [8] : 00000000000000000000000036fdb8800968fb8b38d53ebce3c86f909c796372
Arg [9] : 00000000000000000000000000000000000000000031ef6e017e6033e9a00000
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000168
Arg [11] : 00000000000000000000000000000000000000000000000000000000013c6800
Arg [12] : 00000000000000000000000000000000000000000006cf2f721f56aca1600000
Arg [13] : 000000000000000000000000552002ec9504f3931019d63a8d992c98fcdf1eb7
Arg [14] : 0000000000000000000000000000000000000000003b41ea57328adb16b00000
Arg [15] : 000000000000000000000000000000000000000000000000000000000000012c
Arg [16] : 0000000000000000000000000000000000000000000000000000000000ed4e00
Arg [17] : 00000000000000000000000000000000000000000009a58bb5318a10ffd00000
Arg [18] : 000000000000000000000000719d74be74980f94b2b9ee670f94f540661f9f66
Arg [19] : 00000000000000000000000000000000000000000004a1de0780a6934f100000
Arg [20] : 000000000000000000000000000000000000000000000000000000000000012c
Arg [21] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [22] : 000000000000000000000000000000000000000000012870bb26f3ec17f00000
Arg [23] : 0000000000000000000000004c5e31774b09cf489c482a472bb78ae8c024c701
Arg [24] : 00000000000000000000000000000000000000000003e41c59d913dabc000000
Arg [25] : 00000000000000000000000000000000000000000000000000000000000000d2
Arg [26] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [27] : 00000000000000000000000000000000000000000000bdbc41e0348b30000000
Arg [28] : 0000000000000000000000003ce0935e2f28339d6f9f1068356a90bf6d3173e3
Arg [29] : 000000000000000000000000000000000000000000045348a87272a44a200000
Arg [30] : 00000000000000000000000000000000000000000000000000000000000000d2
Arg [31] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [32] : 00000000000000000000000000000000000000000000e2cb06bdfece5f600000
Arg [33] : 0000000000000000000000009ff1299f5abea9ff1789c86e1c972ac1b3a8d38d
Arg [34] : 0000000000000000000000000000000000000000000445f16bd0aef280c00000
Arg [35] : 00000000000000000000000000000000000000000000000000000000000000b4
Arg [36] : 00000000000000000000000000000000000000000000000000000000004f1a00
Arg [37] : 00000000000000000000000000000000000000000000f022435fc28028c00000
Arg [38] : 000000000000000000000000ca4bde17f2a29fd041665cee2d206bc5c2ae507f
Arg [39] : 00000000000000000000000000000000000000000004389a2f2eeb40b7600000
Arg [40] : 0000000000000000000000000000000000000000000000000000000000000096
Arg [41] : 0000000000000000000000000000000000000000000000000000000000278d00
Arg [42] : 00000000000000000000000000000000000000000000fd7980018631f2200000
Arg [43] : 000000000000000000000000ae0a2dbdfdc0b9d1132b52796d5fcc9a19bd5dc3
Arg [44] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [45] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [46] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [47] : 00000000000000000000000000000000000000000004a1d89bb94865ec000000
Arg [48] : 00000000000000000000000053423085f352a12f0b4c51e00c512e130ae02b38
Arg [49] : 00000000000000000000000000000000000000000029b09d79838b954c000000
Arg [50] : 00000000000000000000000000000000000000000000000000000000000002d0
Arg [51] : 000000000000000000000000000000000000000000000000000000000076a700
Arg [52] : 00000000000000000000000000000000000000000004a1d89bb94865ec000000
Arg [53] : 000000000000000000000000af3bf534d2b026b9c72cb6fb30a5f7f879a6f4f1
Arg [54] : 000000000000000000000000000000000000000000a7eaa0281559f340200000
Arg [55] : 0000000000000000000000000000000000000000000000000000000000000708
Arg [56] : 0000000000000000000000000000000000000000000000000000000003b53800
Arg [57] : 00000000000000000000000000000000000000000000004be4e7267b6ae00000
Arg [58] : 000000000000000000000000344b16fe768582f69205897d240e2971b3ac0c09
Arg [59] : 000000000000000000000000000000000000000000250ec5f358893873d00000
Arg [60] : 00000000000000000000000000000000000000000000000000000000000000b4
Arg [61] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [62] : 0000000000000000000000000000000000000000000943b021e44ac2c4300000
Arg [63] : 000000000000000000000000c5d2d690d683c6f7bb654e91fed293caec2a26b5
Arg [64] : 0000000000000000000000000000000000000000001851a42ee173aa29400000
Arg [65] : 00000000000000000000000000000000000000000000000000000000000002d0
Arg [66] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [67] : 00000000000000000000000000000000000000000004a1e59e6490d2d9c00000
Arg [68] : 00000000000000000000000018bf7f1cf41f840a7cb21618d5d1c1af7ca3e908
Arg [69] : 0000000000000000000000000000000000000000002e524435ac3e59a8a00000
Arg [70] : 0000000000000000000000000000000000000000000000000000000000000438
Arg [71] : 0000000000000000000000000000000000000000000000000000000001da9c00
Arg [72] : 000000000000000000000000000000000000000000000031df9095a18f600000
Arg [73] : 000000000000000000000000fdf5b0d1cd40223ef91027ce014cb4a31ac4ecf2
Arg [74] : 00000000000000000000000000000000000000000029b09d79838b954c000000
Arg [75] : 00000000000000000000000000000000000000000000000000000000000005a0
Arg [76] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [77] : 00000000000000000000000000000000000000000001bccb3fbc0c0014000000


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.