Contract 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb13

 
Txn Hash Method
Block
From
To
Value [Txn Fee]
0x8a1e67cbff37cbd5e1fa448ed40f42eec599d0f43e703125191ea57d6db7bffeInitialize343154862022-11-01 2:05:1288 days 8 hrs ago0x420220b72bbd307db8615e7aa0eadca399cf2fc0 IN  0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH0.00002628
0xac60d0c57c2a2970efaa41badca2fdbdd65c2578e07316d73ed8a7306bd7fe800x60a06040343151442022-11-01 2:03:0488 days 8 hrs ago0x420220b72bbd307db8615e7aa0eadca399cf2fc0 IN  Create: RewardPool0 ETH0.00142904
[ Download CSV Export 
Latest 25 internal transaction
Parent Txn Hash Block From To Value
0x25d772e8154edcbf2d2af3d01e2a0a9f81688343eceecdf1e079b784694bcf4b558789782023-01-27 14:09:3620 hrs 7 mins ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x779815a9473de26e0f9faa2a7ffcfa416182a0d4ba0ebcb5ee47919a6c3e0309556941952023-01-26 20:48:321 day 13 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x779815a9473de26e0f9faa2a7ffcfa416182a0d4ba0ebcb5ee47919a6c3e0309556941952023-01-26 20:48:321 day 13 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x779815a9473de26e0f9faa2a7ffcfa416182a0d4ba0ebcb5ee47919a6c3e0309556941952023-01-26 20:48:321 day 13 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x8c4997e840fd7d01686a8d20526c57d820197f98ac674e3bbab5a08d4dcf8de4556918312023-01-26 20:36:431 day 13 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0xdb9bb31299177e1deed10242831c60d6fa6d7197d1e8ab820c586d73ec1f7d42556847302023-01-26 19:58:381 day 14 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x36957e8a78cfd992e786dda3fa2a837cd676f637a40b0457c37065b7d2df24b5554825702023-01-26 0:28:422 days 9 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x0ae9bc8587e327e623c6c54b36927bd264d02b4111354e555ff18e25d9f1c431551203362023-01-24 11:27:023 days 22 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x92a6fd3cb044cf6a306a538f7cd95f1c636705ea7c71ea05655562537b28d0a7550279762023-01-23 23:59:264 days 10 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x733b62b7a611e91e5d41e4eb2cc3a75358728f78fed95814d850738df4609cf8550130772023-01-23 22:19:134 days 11 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x733b62b7a611e91e5d41e4eb2cc3a75358728f78fed95814d850738df4609cf8550130772023-01-23 22:19:134 days 11 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x733b62b7a611e91e5d41e4eb2cc3a75358728f78fed95814d850738df4609cf8550130772023-01-23 22:19:134 days 11 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x733b62b7a611e91e5d41e4eb2cc3a75358728f78fed95814d850738df4609cf8550130772023-01-23 22:19:134 days 11 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x48fe6c560983a9f6850974f05b2d55bf41e59b641121726f904b42c9cba8b528549505392023-01-23 16:00:504 days 18 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x48fe6c560983a9f6850974f05b2d55bf41e59b641121726f904b42c9cba8b528549505392023-01-23 16:00:504 days 18 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x48fe6c560983a9f6850974f05b2d55bf41e59b641121726f904b42c9cba8b528549505392023-01-23 16:00:504 days 18 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x48fe6c560983a9f6850974f05b2d55bf41e59b641121726f904b42c9cba8b528549505392023-01-23 16:00:504 days 18 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x96e150d53fe922208d4d5eaf92d39c9ed545fdf579483a453093c6eb10d73c22549504072023-01-23 16:00:044 days 18 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x96e150d53fe922208d4d5eaf92d39c9ed545fdf579483a453093c6eb10d73c22549504072023-01-23 16:00:044 days 18 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x7d452784d2ef240cfcdda9a7caf1e5cc58b35a87ce2269d2283ac97fc7b3dac8549338552023-01-23 14:33:074 days 19 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x7d452784d2ef240cfcdda9a7caf1e5cc58b35a87ce2269d2283ac97fc7b3dac8549338552023-01-23 14:33:074 days 19 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x7d452784d2ef240cfcdda9a7caf1e5cc58b35a87ce2269d2283ac97fc7b3dac8549338552023-01-23 14:33:074 days 19 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x1e9d709fde6d0a7bf483efc25a5158914dd26f55d5f5437b1c63acee4a9b0d1f549086962023-01-23 12:08:594 days 22 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x45a2d07f707b97874e7e44c6871dea657131bcf7cc71cf300dd06f3110b034bd546814382023-01-22 9:35:086 days 41 mins ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
0x6fa1283b3f017caa79693567fef9194c40f19f54948cb1afacb3afc064990a46546568422023-01-22 6:37:306 days 3 hrs ago 0xde17af0e4a6c870762508dcb7dcc20719584cbd0 0x3c9ab4f9f587838a72bffe86ce0ebb78449ceb130 ETH
[ Download CSV Export 
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
RewardPool

Compiler Version
v0.8.3+commit.8d00100c

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 28 : RewardPool.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma abicoder v2;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/Bank/IHandleComponent.sol";
import "../interfaces/Bank/IHandle.sol";
import "../interfaces/rewards/IRewardPool.sol";
import "../Bank/Roles.sol";
import "../governance/GovernanceLock.sol";
import "./StakedErc20.sol";

/** @dev Provides scalable pools mapped by protocol reward category IDs.
 *       Categories are dynamic and may have weights adjusted as individual
 *       pools.
 *       Rewards are given in ERC20 (FOREX) and go into each category
 *       proportionally to their weights for the category users to claim.
 *       Alternatively, a specific pool may have FOREX deposited into it
 *       directly, "bypassing" the weights and automatic distribution.
 *       Pools may accept staking of:
 *         [1]: A virtual number (no underlying asset), which relies
 *              on a "staker whitelist". This is used for internal
 *              reward tracking and the stakers are other protocol
 *              contracts which stake for the users.
 *         [2]: An ERC20. Users stake an ERC20 and receive StakedErc20
 *              as a receipt/LP token representing their stake.
 *         [3]: An NFT. Not yet implemented.
 *       Pool weights may be adjusted by an external DAO voting contract
 *       that has the OPERATOR_ROLE role.
 */
contract RewardPool is
    IRewardPool,
    IHandleComponent,
    Initializable,
    UUPSUpgradeable,
    Roles,
    ReentrancyGuardUpgradeable
{
    using SafeERC20 for IERC20;

    /** @dev The Handle contract interface */
    IHandle private handle;
    /** @dev The canonical FOREX token to be used for reward claiming.
     *       The contract assumes it always has sufficient balance to
     *       execute reward claims.
     */
    IERC20 public override forex;
    /** @dev Pool mapping from pool ID to Pool struct */
    mapping(uint256 => Pool) private pools;
    /** @dev Hash alias to pool pool ID */
    mapping(bytes32 => uint256) private poolAliases;
    /** @dev Array of enabled pools */
    uint256[] public enabledPools;
    /** @dev FOREX distribution rate per second */
    uint256 public forexDistributionRate;
    /** @dev Date at which the last distribution of FOREX happened */
    uint256 public lastDistributionDate;
    /** @dev Number of pools created */
    uint256 public poolCount;
    /** @dev The GovernanceLock contract */
    GovernanceLock private governanceLock;
    /** @dev Mapping of pool ID to whether non whitelisted users can stake/unstake */
    mapping(uint256 => bool) public allowNonWhitelistedStakingActions;

    /** @dev Proxy initialisation function */
    function initialize() public initializer {
        __UUPSUpgradeable_init();
        __AccessControl_init();
        __ReentrancyGuard_init();
        _setupRole(ADMIN_ROLE, msg.sender);
        _setRoleAdmin(OPERATOR_ROLE, ADMIN_ROLE);
        _setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE);
    }

    /**
     * @dev Setter for Handle contract reference
     * @param _handle The Handle contract address
     */
    function setHandleContract(address _handle) public override onlyAdmin {
        handle = IHandle(_handle);
        forex = IERC20(handle.forex());
    }

    /**
     * @dev Setter for the GovernanceLock contract reference
     * @param _governanceLock The GovernanceLock contract address
     */
    function setGovernanceLock(address _governanceLock) external onlyAdmin {
        governanceLock = GovernanceLock(_governanceLock);
    }

    /** @dev Getter for Handle contract address */
    function handleAddress() public view override returns (address) {
        return address(handle);
    }

    /**
     * @dev Stakes account into pool to start accruing rewards for pool.
     * @param account The account to stake with.
     * @param value The value to be staked.
     * @param poolId The pool ID to stake into.
     */
    function stake(
        address account,
        uint256 value,
        uint256 poolId
    ) external override nonReentrant returns (uint256 errorCode) {
        Pool storage pool = pools[poolId];
        // Check for staking permission.
        require(
            _canPerformStakingActions(account, poolId),
            "RewardPool: unauthorised"
        );
        require(
            pool.assetType == AssetType.None ||
                pool.assetType == AssetType.ERC20,
            "RewardPool: not yet supported"
        );
        // Try to claim before rewriting user deposit.
        _claimForAccount(account);
        // Load user deposit.
        Deposit storage deposit = pool.deposits[account];
        // Update user deposit.
        _updateDeposit(account, deposit.amount + value, poolId);
        // Mint any receipt tokens after staking as needed.
        _handleAssetsAfterStake(account, value, pool, deposit);
        emit Stake(account, poolId, value);
        return 0;
    }

    /**
     * @dev Returns the boosted stake value for an account and pool.
     * @param value The provided stake value.
     * @param totalRealDeposits The liquidity to calculate the boosted value for.
     * @param boostWeight The account's boost weight for the calculation.
     *        This is equal to the user's gFOREX balance.
     */
    function getUserBoostedStake(
        uint256 value,
        uint256 totalRealDeposits,
        uint256 boostWeight
    ) public view override returns (uint256) {
        // Return base value if the gFOREX data can't be fetched.
        if (address(governanceLock) == address(0) || value == 0) return value;
        uint256 totalSupply = governanceLock.totalSupply();
        // Prevent division by zero if total supply is zero.
        if (totalSupply == 0) return value;
        // The boost deposit amount is an amount of assets
        // deposited into this pool that the user will receive rewards for
        // even though they did not necessarily deposit these assets.
        // That is, if the user staked 100 WETH and their boost is 2.5x then
        // the boost deposit amount is 150 WETH (the user receives an extra reward
        // equivalent to if they had staked an extra 150 WETH).
        // In that example, the (boosted) user deposit is 100 + 150 WETH.
        // The boost deposit amount is calculated as 1.5x of the product of the
        // gFOREX market share of the user and the
        // total (unboosted) deposits in the reward pool.
        // So, as an example, if the user has 50% of all gFOREX supply,
        // the boost deposit amount will be 75% of the total deposits
        // of the reward pool.
        uint256 boostDepositAmount =
            (totalRealDeposits * boostWeight * 15) / (totalSupply * 10);
        // The boost value is added to the unboosted deposit.
        uint256 total = value + boostDepositAmount;
        // Limit the deposit boost to 2.5x.
        uint256 max = (value * 25) / 10;
        // Return the minimum between the boosted total and the value provided.
        return total > max ? max : total;
    }

    /**
     * @dev Unstakes account from a pool to stop accruing rewards.
     * @param account The account to unstake with.
     * @param value The value to be unstaked.
     * @param poolId The poolId ID to unstake from.
     */
    function unstake(
        address account,
        uint256 value,
        uint256 poolId
    ) external override nonReentrant returns (uint256 errorCode) {
        Pool storage pool = pools[poolId];
        // Check for unstaking permission.
        require(
            _canPerformStakingActions(account, poolId),
            "RewardPool: unauthorised"
        );
        Deposit storage deposit = pool.deposits[account];
        // Return early if the account has nothing staked.
        if (deposit.amount == 0) return 1;
        // Try to claim before modifying deposit value due to unstaking.
        _claimForAccount(account);
        uint256 stakeAmount = deposit.amount;
        // Limit value instead of reverting.
        if (value > stakeAmount) value = stakeAmount;
        uint256 newAmount = stakeAmount - value;
        _updateDeposit(account, newAmount, poolId);
        // Burn any receipt tokens after unstaking as needed,
        // and also return funds.
        _handleAssetsAfterUnstake(account, value, pool, deposit);
        emit Unstake(account, poolId, value);
        return 0;
    }

    /**
     * @dev Updates a deposit amount and the S value.
     * @param account The account to update the deposit for.
     * @param amount The new deposit amount.
     * @param poolId The pool ID.
     */
    function _updateDeposit(
        address account,
        uint256 amount,
        uint256 poolId
    ) private {
        Pool storage pool = pools[poolId];
        Deposit storage deposit = pool.deposits[account];
        // Current boosted deposit amount.
        uint256 currentDeposit =
            getUserBoostedStake(
                deposit.amount,
                pool.totalRealDeposits,
                deposit.boostWeight
            );
        // Update boostWeight with user's gFOREX balance if available.
        uint256 newBoostWeight =
            address(governanceLock) != address(0)
                ? governanceLock.balanceOf(account)
                : 0;
        deposit.boostWeight = newBoostWeight;
        // New boosted deposit amount, only used for the totalDeposits variable.
        if (amount == 0) {
            pool.totalDeposits = pool.totalDeposits - currentDeposit;
            pool.totalRealDeposits = pool.totalRealDeposits - deposit.amount;
            delete pool.deposits[account];
            return;
        }
        deposit.S = pool.S;
        pool.totalRealDeposits = deposit.amount > amount // Unstaking.
            ? pool.totalRealDeposits - (deposit.amount - amount) // Staking.
            : pool.totalRealDeposits + (amount - deposit.amount);
        uint256 newDeposit =
            getUserBoostedStake(amount, pool.totalRealDeposits, newBoostWeight);
        pool.totalDeposits = currentDeposit > newDeposit // Unstaking.
            ? pool.totalDeposits - (currentDeposit - newDeposit) // Staking.
            : pool.totalDeposits + (newDeposit - currentDeposit);
        deposit.amount = amount;
    }

    /**
     * @dev Recalculates and applies the current boost for an account.
     *      Ideally this would be execute for all account deposits
     *      at every block, but this is not feasible.
     *      This function allows external actors to make sure
     *      all boosts are fair and sufficiently up to date.
     * @param account The account to update the boost for.
     * @param poolIds An array of pool IDs to update the boost for.
     */
    function updateAccountBoost(address account, uint256[] memory poolIds)
        external
    {
        uint256 length = poolIds.length;
        for (uint256 i = 0; i < length; i++) {
            uint256 poolId = poolIds[i];
            Pool storage pool = pools[poolId];
            require(pool.totalDeposits > 0, "Pool not initialised");
            uint256 currentDepositAmount = pool.deposits[account].amount;
            require(currentDepositAmount > 0, "Account has not deposited");
            // Runs updateDeposit with the current deposit amount,
            // which triggers a recalculation of the boost value
            // using the current block timestamp.
            _updateDeposit(account, currentDepositAmount, poolId);
        }
    }

    /**
     * @dev Claims FOREX rewards for caller.
     */
    function claim() external override nonReentrant {
        _claimForAccount(msg.sender);
    }

    /**
     * @dev Claims the full amount from a pool with id {poolId}
     */
    function claimFromPool(uint256 poolId) external nonReentrant {
        uint256 amount = _calculateAndZeroOutClaim(msg.sender, poolId);
        if (amount == 0) return;
        uint256[] memory poolsClaimed = new uint256[](1);
        uint256[] memory amounts = new uint256[](1);
        poolsClaimed[0] = poolId;
        amounts[0] = amount;

        forex.safeTransfer(msg.sender, amount);
        emit Claim(msg.sender, amount, poolsClaimed, amounts);
    }

    /**
     * @dev Claims FOREX rewards for account.
     */
    function _claimForAccount(address account) private {
        // Update distribution as required before claiming.
        distribute();
        assert(lastDistributionDate == block.timestamp);
        uint256 enabledCount = enabledPools.length;
        uint256[] memory amounts = new uint256[](enabledCount);
        uint256 totalAmount;
        for (uint256 i = 0; i < enabledCount; i++) {
            uint256 poolId = enabledPools[i];
            uint256 amount = _calculateAndZeroOutClaim(account, poolId);
            // Update event variables.
            amounts[i] = amount;
            totalAmount += amount;
        }
        // Return early if there is nothing to claim.
        if (totalAmount == 0) return;
        // Transfer total reward to user.
        forex.safeTransfer(account, totalAmount);
        emit Claim(account, totalAmount, enabledPools, amounts);
    }

    /**
     * @dev Returns the claimable amount in pool with id {poolId} to zero and sets the amount
     * they can claim back down to zero
     */
    function _calculateAndZeroOutClaim(address account, uint256 poolId)
        private
        returns (uint256)
    {
        Pool storage pool = pools[poolId];
        Deposit storage deposit = pool.deposits[account];
        // Cache deposit S value before updating the deposit
        // with the current pool S value.
        // The difference between the pre-update S value
        // and the current pool S value is the delta S
        // used to calculate the claimable balance.
        uint256 depositS = deposit.S;
        // Cache pool S value.
        // This is up to date since this function calls distribute().
        uint256 poolS = pool.S;
        // Cache the gFOREX balance before claiming.
        uint256 boostWeightBeforeClaim = deposit.boostWeight;
        // Refresh boost value & totalDeposits if needed, and update
        // deposit's S value to the current pool's S value, which
        // resets the claimable balance to zero.
        _updateDeposit(account, deposit.amount, poolId);
        // Continue to next iteration if there is nothing to claim.
        if (depositS >= poolS) return 0;
        uint256 boostedAmount =
            getUserBoostedStake(
                deposit.amount,
                pool.totalRealDeposits,
                boostWeightBeforeClaim
            );
        // The difference of the current pool S
        // and the pre-update deposit S
        // is used to calculate the claimable amount for the user.
        uint256 deltaS = poolS - depositS;
        return (boostedAmount * deltaS) / (1 ether);
    }

    /**
     * @dev Creates a new pool.
     * @param weight The pool weight.
     * @param assetType The asset type for the pool.
     * @param assetAddress The asset address for the pool, or zero if none.
     * @param poolIds The array of pools to keep enabled and configure.
     * @param weights The weight array relative to the pools array.
     */
    function createPool(
        uint256 weight,
        AssetType assetType,
        address assetAddress,
        uint256[] memory poolIds,
        uint256[] memory weights
    ) external override onlyOperatorOrAdmin {
        _setPools(poolIds, weights);
        // Create new pool.
        uint256 newPoolId = poolCount++;
        Pool storage pool = pools[newPoolId];
        _initialisePool(pool, assetType, assetAddress, weight, newPoolId);
        // Add to enabled pools only if the weight is not zero.
        if (weight > 0) _addToEnabledPools(newPoolId);
    }

    /**
     * @dev Allows for setting up multiple pools at once after deployment.
     * @param assetTypes The asset type array for each pool.
     * @param assetAddresses The asset addresses array for each pool.
     * @param weights The weight array relative to the pools array.
     * @param aliases The aliases for the pools.
     */
    function setupPools(
        AssetType[] memory assetTypes,
        address[] memory assetAddresses,
        uint256[] memory weights,
        bytes32[] memory aliases
    ) external override onlyOperatorOrAdmin {
        require(poolCount == 0, "Pools already initialised");
        uint256 count = assetTypes.length;
        assert(count == assetAddresses.length && count == weights.length);
        uint256[] memory poolIds = new uint256[](count);
        for (uint256 i = 0; i < count; i++) {
            Pool storage pool = pools[i];
            poolIds[i] = i;
            _initialisePool(
                pool,
                assetTypes[i],
                assetAddresses[i],
                weights[i],
                i
            );
            setPoolAlias(aliases[i], i);
        }
        enabledPools = poolIds;
        poolCount = count;
    }

    function _initialisePool(
        Pool storage pool,
        AssetType assetType,
        address assetAddress,
        uint256 weight,
        uint256 poolId
    ) private {
        _validatePoolAssetTypeAndAddress(assetType, assetAddress);
        pool.assetType = assetType;
        pool.assetAddress = assetAddress;
        pool.weight = weight;
        if (assetAddress != address(0))
            _setWhitelistedStaker(assetAddress, poolId, true);
        emit CreatePool(poolId, assetType, assetAddress, weight);
    }

    /**
     * @dev Used to enable and disable pools.
     *      To disable, a weight must be set to zero.
     *      Will emit events accordingly.
     * @param poolIds] The array of poolIds] to configure.
     * @param weights The array of weights to configure.
     */
    function setPools(uint256[] memory poolIds, uint256[] memory weights)
        public
        override
        onlyOperatorOrAdmin
    {
        _setPools(poolIds, weights);
    }

    /**
     * @dev Used to enable and disable pools.
     *      All enabled pools must be within the poolIds and weights arrays,
     *      and they should have a weight greater than zero.
     *      Any pools not in poolIds will have their weights set to zero.
     *      Will emit events accordingly.
     * @param poolIds The array of pool IDs to configure.
     * @param weights The array of weights to configure.
     */
    function _setPools(uint256[] memory poolIds, uint256[] memory weights)
        private
    {
        uint256 argumentLength = poolIds.length;
        require(
            argumentLength == weights.length,
            "RewardPool: args length mismatch"
        );
        // Cache storage read.
        uint256 count = poolCount;
        // Set pool weights as required.
        for (uint256 i = 0; i < count; i++) {
            // Find whether this pool has been passed in the poolIds argument.
            bool inArgument = false;
            uint256 argumentIndex;
            for (uint256 j = 0; j < argumentLength; j++) {
                if (i != poolIds[j]) continue;
                inArgument = true;
                argumentIndex = j;
                break;
            }
            Pool storage pool = pools[i];
            if (!inArgument) {
                // If the pool was not passed in the argument,
                // set its weight to zero.
                pool.weight = 0;
                continue;
            }
            uint256 weight = weights[argumentIndex];
            require(weight > 0, "RewardPool: set zero-weight pool");
            // Set pool weight to the argument value.
            pool.weight = weight;
        }
        enabledPools = poolIds;
        emit SetPoolWeights(poolIds, weights);
    }

    /**
     * Returns whether the msg.sender can perform a staking action on behalf of {account}
     * in pool with id {poolId}.
     * @param account The account for which to perform staking actions on.
     * @param poolId The pool id to perform a staking action on.
     * @return canPerformStakingAction Whether or not the msg.sender can perform a
     * staking action.
     */
    function _canPerformStakingActions(address account, uint256 poolId)
        private
        view
        returns (bool)
    {
        bool isWhitelisted = pools[poolId].stakerWhitelist[msg.sender];
        // If msg.sender is whitelisted, msg.sender can always unstake.
        if (isWhitelisted) return true;
        // If msg.sender is not whitelisted, they can unstake if they are unstaking on behalf
        // of themselves, and the pool allows non-whitelisted accounts to perform staking actions.
        return
            account == msg.sender && allowNonWhitelistedStakingActions[poolId];
    }

    /**
     * @dev Set the FOREX distribution rate per second.
     * @param rate The FOREX rate per second to distribute across pools.
     */
    function setForexDistributionRate(uint256 rate)
        external
        override
        onlyOperatorOrAdmin
    {
        // Return early if rates are the same.
        if (rate == forexDistributionRate) return;
        // Distribute outstanding FOREX before updating the rate.
        if (lastDistributionDate != block.timestamp) {
            distribute();
            assert(lastDistributionDate == block.timestamp);
        }
        forexDistributionRate = rate;
        emit SetForexDistributionRate(rate);
    }

    /**
     * @dev Distributes outstanding FOREX across pools.
     *      Updates the lastDistributionDate to the current block timestamp
     *      if successful.
     */
    function distribute() public override {
        // Return early after running once this block.
        if (block.timestamp == lastDistributionDate) return;
        (, uint256[] memory amounts, uint256[] memory deltaS, ) =
            getEnabledPoolsData();
        // Calculate seconds passed since last distribution.
        uint256 duration = block.timestamp - lastDistributionDate;
        // Update the last distribution date.
        lastDistributionDate = block.timestamp;
        // Return early if there are no amounts to distribute.
        if (amounts.length == 0) return;
        assert(amounts.length == deltaS.length);
        uint256 totalAmount = 0;
        // Cache storage read.
        uint256 count = enabledPools.length;
        for (uint256 i = 0; i < count; i++) {
            Pool storage pool = pools[enabledPools[i]];
            // Continue if there are no amounts or pool is disabled.
            if (amounts[i] == 0 || pool.weight == 0) continue;
            pool.S += deltaS[i];
            totalAmount += amounts[i];
        }
        emit ForexDistributed(
            duration,
            forexDistributionRate,
            totalAmount,
            enabledPools,
            amounts
        );
    }

    function distributeDirectlyForPool(uint256 poolId, uint256 amount)
        external
        override
        onlyOperatorOrAdmin
    {
        Pool storage pool = pools[poolId];
        require(pool.totalDeposits > 0, "RewardPool: pool is empty");
        // Add delta S value based on distribution amount.
        pool.S += (amount * (1 ether)) / pool.totalDeposits;
        emit ForexDistributedDirectly(poolId, amount);
    }

    /**
     * @dev Sets or unsets a staking address as whitelisted from a pool.
     *      The whitelist is only used if the pool's asset type is None.
     */
    function setWhitelistedStaker(
        address staker,
        uint256 poolId,
        bool isWhitelisted
    ) external override onlyOperatorOrAdmin {
        _setWhitelistedStaker(staker, poolId, isWhitelisted);
    }

    /**
     * @dev Sets whether a non-whitelsited user can stake into a pool ID.
     */
    function setAllowNonWhitelistedStakingActions(uint256 poolId, bool allow)
        external
        onlyOperatorOrAdmin
    {
        require(
            allowNonWhitelistedStakingActions[poolId] != allow,
            "RewardPool: already set"
        );
        allowNonWhitelistedStakingActions[poolId] = allow;
        emit AllowNonWhitelistStakingActionChanged(poolId, allow);
    }

    /**
     * @dev Sets or unsets a staking address as whitelisted from a pool.
     *      The whitelist is only used if the pool's asset type is None.
     */
    function _setWhitelistedStaker(
        address staker,
        uint256 poolId,
        bool isWhitelisted
    ) private {
        require(
            pools[poolId].stakerWhitelist[staker] != isWhitelisted,
            "RewardPool: already set"
        );
        pools[poolId].stakerWhitelist[staker] = isWhitelisted;
        emit WhitelistChanged(staker, poolId, isWhitelisted);
    }

    /** @dev Sets a pool hash alias. These are used to fetch similar pools
     *       of pools without relying on the ID directly, e.g. all minting
     *       rewards pool for the protocol fxTokens. The ID for those may be
     *       obtained by e.g. getPoolIdByAlias(keccak256("minting" + tokenAddress))
     */
    function setPoolAlias(bytes32 hash, uint256 poolId)
        public
        override
        onlyOperatorOrAdmin
    {
        // The poolId gets added by 1 so that a value of zero can revert
        // to identify aliases that have not been set (see getPoolIdByAlias).
        poolAliases[hash] = poolId + 1;
        emit PoolAliasChanged(poolId, hash);
    }

    /**
     * @dev Adds an item to the enabledPools array
     * @param poolId The poolId ID to push to the array.
     */
    function _addToEnabledPools(uint256 poolId) private {
        uint256 count = enabledPools.length;
        for (uint256 i = 0; i < count; i++) {
            if (enabledPools[i] != poolId) continue;
            revert("RewardPool: pushing duplicate pool ID");
        }
        enabledPools.push(poolId);
    }

    /**
     * @dev Calculates pool parameters for all enabled pools from the
     *      current block.
     *      Also returns an array of the enabled pool IDs.
     */
    function getEnabledPoolsData()
        public
        view
        override
        returns (
            uint256[] memory poolRatios,
            uint256[] memory accruedAmounts,
            uint256[] memory deltaS,
            uint256[] memory poolIds
        )
    {
        uint256 enabledCount = enabledPools.length;
        poolRatios = new uint256[](enabledCount);
        accruedAmounts = new uint256[](enabledCount);
        deltaS = new uint256[](enabledCount);
        poolIds = new uint256[](enabledCount);
        // Return early if there no pools enabled as all arrays are empty.
        if (enabledCount == 0)
            return (poolRatios, accruedAmounts, deltaS, poolIds);
        // Get total pools weight to calculate ratios.
        uint256 totalWeight = _getTotalWeight();
        // Initialise poolIds and ratios array.
        for (uint256 i = 0; i < enabledCount; i++) {
            // Set poolIds for this index.
            poolIds[i] = enabledPools[i];
            // Load pool from storage and set poolRatios for this index.
            Pool storage pool = pools[poolIds[i]];
            if (totalWeight == 0 || pool.weight == 0) continue;
            poolRatios[i] = (pool.weight * (1 ether)) / totalWeight;
        }
        // Check whether the function should return before the
        // reward accrual calculations.
        bool shouldReturnEarly =
            // Return early after running once this block, as
            // that means all the accrued and delta values are zero.
            block.timestamp == lastDistributionDate ||
                // Return early if rate is zero, for the same reason above.
                // Whenever the rate changes, a re-distribution occurs.
                // So if the rate is zero, the amount below would also be.
                forexDistributionRate == 0;
        if (shouldReturnEarly)
            return (poolRatios, accruedAmounts, deltaS, poolIds);
        uint256 rate = forexDistributionRate;
        uint256 duration = block.timestamp - lastDistributionDate;
        uint256 amount = duration * rate;
        for (uint256 i = 0; i < enabledCount; i++) {
            // Load pool from storage.
            Pool storage pool = pools[poolIds[i]];
            if (pool.weight == 0 || pool.totalDeposits == 0) continue;
            accruedAmounts[i] = (amount * poolRatios[i]) / (1 ether);
            deltaS[i] = (accruedAmounts[i] * (1 ether)) / pool.totalDeposits;
        }
    }

    /**
     * @dev Calculates delta S value for a single pool.
     */
    function _getPoolDeltaS(Pool storage pool) private view returns (uint256) {
        uint256 totalWeight = _getTotalWeight();
        uint256 totalDeposits = pool.totalDeposits;
        if (totalWeight == 0 || totalDeposits == 0) return 0;
        uint256 rate = forexDistributionRate;
        uint256 duration = block.timestamp - lastDistributionDate;
        // Amount accrued across all pools
        uint256 globalAmount = duration * rate;
        uint256 poolRatio = (pool.weight * (1 ether)) / totalWeight;
        uint256 poolAccruedAmount = (globalAmount * poolRatio) / (1 ether);
        return (poolAccruedAmount * (1 ether)) / totalDeposits;
    }

    /**
     * @dev Returns a reward pool alias for the fxToken and pool category.
     * @param token The fxToken address to get the pool for.
     * @param category The reward category number.
     */
    function getFxTokenPoolAlias(address token, uint256 category)
        external
        pure
        override
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(token, category));
    }

    /** @dev Returns a pool hash alias or reverts if not defined */
    function getPoolIdByAlias(bytes32 hash)
        public
        view
        override
        returns (bool found, uint256 poolId)
    {
        uint256 result = poolAliases[hash];
        if (result == 0) return (false, 0);
        return (true, result - 1);
    }

    /**
     * @dev Get pool data.
     * @param poolId The ID of the pool to view.
     */
    function getPool(uint256 poolId)
        external
        view
        override
        returns (
            uint256 weight,
            AssetType assetType,
            address assetAddress,
            uint256 totalDeposits,
            uint256 S,
            uint256 totalRealDeposits
        )
    {
        Pool storage pool = pools[poolId];
        return (
            pool.weight,
            pool.assetType,
            pool.assetAddress,
            pool.totalDeposits,
            pool.S,
            pool.totalRealDeposits
        );
    }

    /**
     * @dev Get deposit for address.
     * @param account The account to view.
     * @param poolId The ID of the pool to view.
     */
    function getDeposit(address account, uint256 poolId)
        external
        view
        override
        returns (Deposit memory)
    {
        return pools[poolId].deposits[account];
    }

    /**
     * @dev Returns the reward balance for an address.
     * @param account The address to fetch balance for.
     */
    function balanceOf(address account)
        public
        view
        override
        returns (uint256 balance)
    {
        (, , uint256[] memory deltaS, ) = getEnabledPoolsData();
        uint256 count = enabledPools.length;
        for (uint256 i = 0; i < count; i++) {
            uint256 poolId = enabledPools[i];
            Pool storage pool = pools[poolId];
            Deposit storage deposit = pool.deposits[account];
            uint256 poolS = pool.S + deltaS[i];
            balance += _calculateClaimBalance(
                poolS,
                pool.totalRealDeposits,
                deposit
            );
        }
    }

    /**
     * @dev Returns the reward balance for an address on a specific pool.
     * @param account The address to fetch balance for.
     * @param poolId The pool ID to fetch the balance for.
     */
    function poolBalanceOf(address account, uint256 poolId)
        external
        view
        override
        returns (uint256 balance)
    {
        Pool storage pool = pools[poolId];
        Deposit storage deposit = pool.deposits[account];
        // Get pool S including accrual not yet distributed.
        uint256 poolS = pool.S + _getPoolDeltaS(pool);
        return _calculateClaimBalance(poolS, pool.totalRealDeposits, deposit);
    }

    /**
     * @dev Returns the (boosted) reward balance on a specific pool.
     * @param poolS The pool S value to use for the calculation.
     * @param poolTotalRealDeposits The (non-boosted) pool deposits.
     * @param deposit The reference deposit struct for the account.
     */
    function _calculateClaimBalance(
        uint256 poolS,
        uint256 poolTotalRealDeposits,
        Deposit storage deposit
    ) private view returns (uint256) {
        uint256 depositS = deposit.S;
        // Get boosted deposit amount.
        uint256 depositAmount =
            getUserBoostedStake(
                deposit.amount,
                poolTotalRealDeposits,
                deposit.boostWeight
            );
        // Deposit S should never be greater than pool S,
        // but it could be equal.
        if (depositAmount == 0 || depositS >= poolS) return 0;
        uint256 deltaS = poolS - depositS;
        return (depositAmount * deltaS) / (1 ether);
    }

    /**
     * @dev View to return the length of the enabledPools array,
     *      because Solidity is a special snowflake.
     */
    function enabledPoolsLength() external view returns (uint256) {
        return enabledPools.length;
    }

    /**
     * @dev Returns the total weight for all enabled pools.
     */
    function _getTotalWeight() private view returns (uint256 totalWeight) {
        uint256 enabledCount = enabledPools.length;
        for (uint256 i = 0; i < enabledCount; i++) {
            totalWeight += pools[enabledPools[i]].weight;
        }
    }

    /**
     * @dev Sets totalRealDeposits to totalDeposits for all pools.
     *      Only works if totalRealDeposits is zero and totalDeposits is not.
     *      Should only be called before boosts are active due to the
     *      totalRealDeposits variable being added to the code as a fix.
     */
    function syncTotalRealDeposits() external onlyAdmin {
        uint256 count = poolCount;
        for (uint256 i = 0; i < count; i++) {
            Pool storage pool = pools[i];
            if (pool.totalDeposits == 0) continue;
            pool.totalRealDeposits = pool.totalDeposits;
        }
    }

    /**
     * @dev Behaviour to execute after user deposit is updated when
     *      staking into a pool.
     *      This triggers the collection of an ERC20 asset if the
     *      pool requires ERC20 staking, and mints receipt tokens.
     * @param account The address of the user staking.
     * @param amount The amount being staked.
     * @param pool The reward pool struct reference.
     * @param deposit The deposit struct reference for the user.
     */
    function _handleAssetsAfterStake(
        address account,
        uint256 amount,
        Pool storage pool,
        Deposit storage deposit
    ) private {
        if (pool.assetType != AssetType.ERC20) return;
        _depositErc20(amount, pool);
        _mintStakedErc20(account, amount, pool, deposit);
    }

    /**
     * @dev Behaviour to execute after user deposit is updated when
     *      unstaking from a pool.
     *      This triggers the return of an ERC20 asset if the
     *      pool requires ERC20 staking, and burns receipt tokens.
     * @param account The address of the user unstaking.
     * @param amount The amount being unstaked.
     * @param pool The reward pool struct reference.
     * @param deposit The deposit struct reference for the user.
     */
    function _handleAssetsAfterUnstake(
        address account,
        uint256 amount,
        Pool storage pool,
        Deposit storage deposit
    ) private {
        if (pool.assetType != AssetType.ERC20) return;
        _withdrawErc20(amount, pool);
        _burnStakedErc20(account, amount, pool, deposit);
    }

    /**
     * @dev Transfers in the deposit of ERC20 tokens from the sender.
     * @param amount The amount being staked.
     * @param pool The reward pool struct reference.
     */
    function _depositErc20(uint256 amount, Pool storage pool) private {
        assert(pool.assetAddress != address(0));
        StakedErc20 stakedErc20 = StakedErc20(pool.assetAddress);
        IERC20 depositToken = IERC20(stakedErc20.depositToken());
        depositToken.safeTransferFrom(msg.sender, address(this), amount);
    }

    /**
     * @dev Transfers out the deposit of ERC20 tokens to the sender.
     * @param amount The amount being unstaked.
     * @param pool The reward pool struct reference.
     */
    function _withdrawErc20(uint256 amount, Pool storage pool) private {
        assert(pool.assetAddress != address(0));
        StakedErc20 stakedErc20 = StakedErc20(pool.assetAddress);
        IERC20 depositToken = IERC20(stakedErc20.depositToken());
        depositToken.safeTransfer(msg.sender, amount);
    }

    /**
     * @dev Mints receipt ERC20 tokens to the user, ensuring their
     *      token balance is the same as their deposit amount.
     * @param account The address of the user staking.
     * @param amount The amount being staked.
     * @param pool The reward pool struct reference.
     * @param deposit The deposit struct reference for the user.
     */
    function _mintStakedErc20(
        address account,
        uint256 amount,
        Pool storage pool,
        Deposit storage deposit
    ) private {
        // The user's receipt token balance must be equal to
        // the user's deposit in the pool.
        StakedErc20 token = StakedErc20(pool.assetAddress);
        token.mint(account, amount);
        // This check is made in StakedErc20 prior to mint,
        // and also replicated here after mint.
        assert(deposit.amount == token.balanceOf(account));
    }

    /**
     * @dev Burns receipt ERC20 tokens to the user, ensuring their
     *      token balance is the same as their deposit amount.
     * @param account The address of the user unstaking.
     * @param amount The amount being unstaked.
     * @param pool The reward pool struct reference.
     * @param deposit The deposit struct reference for the user.
     */
    function _burnStakedErc20(
        address account,
        uint256 amount,
        Pool storage pool,
        Deposit storage deposit
    ) private {
        StakedErc20 token = StakedErc20(pool.assetAddress);
        token.burn(account, amount);
        // This check is made in StakedErc20 prior to burn,
        // and also replicated here after burn.
        assert(deposit.amount == token.balanceOf(account));
    }

    /**
     * @dev Validates that the asset type and address are correct.
     *      If the asset type is None, then the address must be zero.
     *      If it is not none, then the address must not be zero.
     * @param assetType The asset type.
     * @param assetAddress The asset address.
     */
    function _validatePoolAssetTypeAndAddress(
        AssetType assetType,
        address assetAddress
    ) private pure {
        // If the asset type is None, then the address must be zero.
        bool isValidNoAsset =
            (assetType == AssetType.None && assetAddress == address(0));
        // If the asset type is not None, then the address must not be zero.
        bool isValidAsset =
            (assetType != AssetType.None && assetAddress != address(0));
        require(
            isValidNoAsset || isValidAsset,
            "RewardPool: invalid type/address"
        );
    }

    /** @dev Protected UUPS upgrade authorization function */
    function _authorizeUpgrade(address) internal override onlyAdmin {}
}

File 2 of 28 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/utils/Initializable.sol)

pragma solidity ^0.8.0;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
 * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() initializer {}
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        // If the contract is initializing we ignore whether _initialized is set in order to support multiple
        // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
        // contract may have been reentered.
        require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} modifier, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    function _isConstructor() private view returns (bool) {
        return !AddressUpgradeable.isContract(address(this));
    }
}

File 3 of 28 : ReentrancyGuardUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";

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

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

    uint256 private _status;

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

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
    uint256[49] private __gap;
}

File 4 of 28 : UUPSUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.0;

import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
import "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 *
 * _Available since v4.1._
 */
abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable {
    function __UUPSUpgradeable_init() internal onlyInitializing {
        __ERC1967Upgrade_init_unchained();
        __UUPSUpgradeable_init_unchained();
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
    address private immutable __self = address(this);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        require(address(this) != __self, "Function must be called through delegatecall");
        require(_getImplementation() == __self, "Function must be called through active proxy");
        _;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     */
    function upgradeTo(address newImplementation) external virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, data, true);
    }

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

File 5 of 28 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../../../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;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @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, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 6 of 28 : IHandleComponent.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma abicoder v2;

interface IHandleComponent {
    function setHandleContract(address hanlde) external;

    function handleAddress() external view returns (address);
}

File 7 of 28 : IHandle.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

pragma abicoder v2;

interface IHandle {
    struct Vault {
        // Collateral token address => balance
        mapping(address => uint256) collateralBalance;
        uint256 debt;
        // Collateral token address => R0
        mapping(address => uint256) R0;
    }

    struct CollateralData {
        uint256 mintCR;
        uint256 liquidationFee;
        uint256 interestRate;
    }

    event UpdateDebt(address indexed account, address indexed fxToken);

    event UpdateCollateral(
        address indexed account,
        address indexed fxToken,
        address indexed collateralToken
    );

    event ConfigureCollateralToken(address indexed collateralToken);

    event ConfigureFxToken(address indexed fxToken, bool removed);

    function setCollateralUpperBoundPCT(uint256 ratio) external;

    function setPaused(bool value) external;

    function setFxToken(address token) external;

    function removeFxToken(address token) external;

    function setCollateralToken(
        address token,
        uint256 mintCR,
        uint256 liquidationFee,
        uint256 interestRatePerMille
    ) external;

    function removeCollateralToken(address token) external;

    function getAllCollateralTypes()
        external
        view
        returns (address[] memory collateral);

    function getCollateralDetails(address collateral)
        external
        view
        returns (CollateralData memory);

    function WETH() external view returns (address);

    function treasury() external view returns (address payable);

    function comptroller() external view returns (address);

    function vaultLibrary() external view returns (address);

    function fxKeeperPool() external view returns (address);

    function pct() external view returns (address);

    function liquidator() external view returns (address);

    function interest() external view returns (address);

    function referral() external view returns (address);

    function forex() external view returns (address);

    function rewards() external view returns (address);

    function pctCollateralUpperBound() external view returns (uint256);

    function isFxTokenValid(address fxToken) external view returns (bool);

    function isCollateralValid(address collateral) external view returns (bool);

    function setComponents(address[] memory components) external;

    function updateDebtPosition(
        address account,
        uint256 amount,
        address fxToken,
        bool increase
    ) external;

    function updateCollateralBalance(
        address account,
        uint256 amount,
        address fxToken,
        address collateralToken,
        bool increase
    ) external;

    function setFeeRecipient(address feeRecipient) external;

    function setFees(
        uint256 withdrawFeePerMille,
        uint256 depositFeePerMille,
        uint256 mintFeePerMille,
        uint256 burnFeePerMille
    ) external;

    function getCollateralBalance(
        address account,
        address collateralType,
        address fxToken
    ) external view returns (uint256 balance);

    function getBalance(address account, address fxToken)
        external
        view
        returns (address[] memory collateral, uint256[] memory balances);

    function getDebt(address owner, address fxToken)
        external
        view
        returns (uint256 _debt);

    function getPrincipalDebt(address owner, address fxToken)
        external
        view
        returns (uint256 _debt);

    function getCollateralR0(
        address account,
        address fxToken,
        address collateral
    ) external view returns (uint256 R0);

    function getTokenPrice(address token) external view returns (uint256 quote);

    function setOracle(address fxToken, address oracle) external;

    function FeeRecipient() external view returns (address);

    function mintFeePerMille() external view returns (uint256);

    function burnFeePerMille() external view returns (uint256);

    function withdrawFeePerMille() external view returns (uint256);

    function depositFeePerMille() external view returns (uint256);

    function isPaused() external view returns (bool);
}

File 8 of 28 : IRewardPool.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma abicoder v2;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @dev Established pool categories to be used internally by the
 * protocol for the users.
 * This enum is used to get the correct pool ID from the pool alias
 * which consists of an fxToken address and a category number.
 */
enum RewardPoolCategory {Mint, Deposit, Keeper}

interface IRewardPool {
    /** Reward category e.g. Keepers or Liquidity Providers */
    struct Pool {
        // Pool reward weight. The amount of FOREX allocated to this
        // category is given by the ratio of this weight to the sum of all
        // weights for enabled pools.
        // A category is enabled if the weight is not zero.
        uint256 weight;
        // Asset is used for pools that require token staking,
        // such as LP tokens. Could be e.g. None, ERC20 or ERC721.
        AssetType assetType;
        // The address of the StakedErc20 for this pool.
        // This is the receipt token, not the deposit/underlying asset.
        // For pools with no token (AssetType.None), this is the zero address.
        address assetAddress;
        // If AssetType == None, whitelist with this map from staker address.
        mapping(address => bool) stakerWhitelist;
        // Account -> Deposit
        mapping(address => Deposit) deposits;
        // Total amount deposited, which may be boosted.
        uint256 totalDeposits;
        // Current pool reward ratio over total deposits.
        uint256 S;
        // Total real value deposited (excluding boosts).
        uint256 totalRealDeposits;
    }

    /** Reward pool deposit for tracking user contributions */
    struct Deposit {
        // Amount contributed.
        // e.g. for Keepers, the total amount staked.
        //      for minters, the total amount minted.
        //      for LPs, the total liquidity provided.
        uint256 amount;
        // Reward ratio over total deposits during deposit.
        uint256 S;
        // The weight (gFOREX balance) by which the boost is
        // calculated from during claim.
        uint256 boostWeight;
    }

    enum AssetType {None, ERC20, ERC721}

    event Stake(address indexed account, uint256 poolId, uint256 amount);

    event Unstake(address indexed account, uint256 poolId, uint256 amount);

    event CreatePool(
        uint256 id,
        AssetType assetType,
        address asset,
        uint256 weight
    );

    event SetPoolWeights(uint256[] poolIds, uint256[] weights);

    event SetFxTokenWeights(address[] fxTokens, uint256[] weights);

    event ForexDistributed(
        uint256 duration,
        uint256 rate,
        uint256 totalAmount,
        uint256[] poolIds,
        uint256[] amounts
    );

    event ForexDistributedDirectly(uint256 poolId, uint256 amount);

    event SetForexDistributionRate(uint256 ratePerSecond);

    event Claim(
        address indexed acount,
        uint256 amount,
        uint256[] poolIds,
        uint256[] amounts
    );

    event WhitelistChanged(address staker, uint256 poolId, bool whitelisted);
    event AllowNonWhitelistStakingActionChanged(
        uint256 indexed poolId,
        bool ignoreWhitelist
    );

    event PoolAliasChanged(uint256 poolId, bytes32 aliasHash);

    function stake(
        address account,
        uint256 value,
        uint256 poolId
    ) external returns (uint256 errorCode);

    function unstake(
        address account,
        uint256 value,
        uint256 poolId
    ) external returns (uint256 errorCode);

    function claim() external;

    function distribute() external;

    function distributeDirectlyForPool(uint256 poolId, uint256 amount) external;

    function createPool(
        uint256 weight,
        AssetType assetType,
        address assetAddress,
        uint256[] memory poolIds,
        uint256[] memory weights
    ) external;

    function setupPools(
        AssetType[] memory assetTypes,
        address[] memory assetAddresses,
        uint256[] memory weights,
        bytes32[] memory aliases
    ) external;

    // Used to enable and disable pools.
    // To disable, a weight must be set to zero.
    // Will emit events accordingly.
    function setPools(uint256[] memory poolIds, uint256[] memory weights)
        external;

    function setWhitelistedStaker(
        address staker,
        uint256 poolId,
        bool isWhitelisted
    ) external;

    function setForexDistributionRate(uint256 rate) external;

    function setPoolAlias(bytes32 hash, uint256 poolId) external;

    function getEnabledPoolsData()
        external
        view
        returns (
            uint256[] memory poolRatios,
            uint256[] memory accruedAmounts,
            uint256[] memory deltaS,
            uint256[] memory poolIds
        );

    function getPoolIdByAlias(bytes32 hash)
        external
        view
        returns (bool found, uint256 poolId);

    function getFxTokenPoolAlias(address token, uint256 category)
        external
        pure
        returns (bytes32);

    // Return allowed parameters only (no mappings)
    // because Solidity is a Special Snowflake (tm)
    function getPool(uint256 poolId)
        external
        view
        returns (
            uint256 weight,
            AssetType assetType,
            address assetAddress,
            uint256 totalDeposits,
            uint256 S,
            uint256 totalRealDeposits
        );

    function getDeposit(address account, uint256 poolId)
        external
        view
        returns (Deposit memory);

    function balanceOf(address account) external view returns (uint256 balance);

    function poolBalanceOf(address account, uint256 poolId)
        external
        view
        returns (uint256 balance);

    function getUserBoostedStake(
        uint256 value,
        uint256 poolId,
        uint256 boostWeight
    ) external view returns (uint256);

    function forex() external view returns (IERC20);
}

File 9 of 28 : Roles.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

abstract contract Roles is AccessControlUpgradeable {
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    modifier onlyOperator() {
        require(hasRole(OPERATOR_ROLE, msg.sender), "NO");
        _;
    }

    modifier onlyAdmin() {
        require(hasRole(ADMIN_ROLE, msg.sender), "NA");
        _;
    }

    modifier onlyOperatorOrAdmin() {
        require(
            hasRole(OPERATOR_ROLE, msg.sender) ||
                hasRole(ADMIN_ROLE, msg.sender),
            "NW"
        );
        _;
    }

    modifier onlyAddressOrOperatorExcludeAdmin(address addressAllowed) {
        // Protect user deposits from abuse
        require(
            msg.sender == addressAllowed ||
                (hasRole(OPERATOR_ROLE, msg.sender) &&
                    !hasRole(ADMIN_ROLE, msg.sender)),
            "NW"
        );
        _;
    }
}

File 10 of 28 : GovernanceLock.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma abicoder v2;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/rewards/IRewardPool.sol";

struct Point {
    int128 bias;
    int128 slope;
    uint256 ts;
    uint256 blk; // block
}

struct LockedBalance {
    int128 amount;
    uint256 end;
}

/**
 * @dev Allows FOREX holders to lock tokens for gFOREX and gain Handle DAO voting power.
 *      FOREX may be locked for up to 4 years.
 *      gFOREX balance decays linearly during the locked period until FOREX is fully unlocked.
 *      A locked position may be added to or have its duration extended at any time for an
 *      increase in gFOREX voting power.
 */
contract GovernanceLock is
    Initializable,
    UUPSUpgradeable,
    OwnableUpgradeable,
    ReentrancyGuardUpgradeable
{
    using SafeERC20 for IERC20;

    int128 public constant MAX_TIME = 4 * 365 * 86400; // 4 years
    bytes32 public constant REWARD_POOL_ALIAS = keccak256("governancelock");

    /** @dev The token to be locked. e.g. FOREX */
    address public token;
    /** @dev The supply of token's locked */
    uint256 public supply;
    /** @dev Mapping from account address to locked balance position */
    mapping(address => LockedBalance) public locked;
    /** @dev The current system epoch */
    uint256 public epoch;
    /** @dev Mapping from epoch to point history */
    mapping(uint256 => Point) public pointHistory;
    /** @dev Mapping from account address to epoch to point history */
    mapping(address => mapping(uint256 => Point)) public userPointHistory;
    /** @dev Mapping from account address to current user epoch */
    mapping(address => uint256) public userPointEpoch;
    /** @dev Mapping from slope changes @ "week time" to slope value */
    mapping(uint256 => int128) public slopeChanges;
    /** @dev Mapping from contract address to its whitelisted status */
    mapping(address => bool) public whitelistedContracts;
    /** @dev Whether the whitelist is enabled for contract access */
    bool public isWhitelistEnabled;
    /** @dev The Handle reward pool for rewarding locking */
    IRewardPool public rewardPool;

    uint256 public constant WEEK = 7 * 86400;
    uint256 public constant MULTIPLIER = 1 ether;

    /** @dev Whether the contract has been retired and token refunds are on */
    bool public retiredContract;

    enum DepositType {
        DepositFor,
        CreateLock,
        IncreaseLockAmount,
        IncreaseUnlockTime
    }

    event Deposit(
        address indexed depositor,
        uint256 value,
        uint256 indexed locktime,
        DepositType depositType,
        uint256 ts
    );
    event Withdraw(address indexed depositor, uint256 value, uint256 ts);
    event Supply(uint256 previousSupply, uint256 supply);

    /**
     * @dev Reverts the tranasction if the sender is a contract and not
     *      whitelisted.
     */
    modifier onlyAllowedLocker() {
        require(
            !isWhitelistEnabled ||
                !isContract(msg.sender) ||
                whitelistedContracts[msg.sender],
            "Contract not allowed"
        );
        _;
    }

    /** @dev Proxy initialisation function */
    function initialize(address tokenAddress, address rewardPoolAddress)
        public
        initializer
        onlyProxy
    {
        __UUPSUpgradeable_init();
        __Ownable_init();
        __ReentrancyGuard_init();
        token = tokenAddress;
        rewardPool = IRewardPool(rewardPoolAddress);
        isWhitelistEnabled = true;
        pointHistory[0].blk = block.number;
        pointHistory[0].ts = block.timestamp;
    }

    /**
     * @dev "Retires" the contract by allowing refunds and preventing new deposits.
     * @param isRetired Whether to retire the contract.
     */
    function retireContract(bool isRetired) external onlyOwner {
        retiredContract = isRetired;
    }

    /**
     * @dev Adds or removes a contract access from the whitelist.
     */
    function setContractWhitelist(address contractAddress, bool isWhitelisted)
        external
        onlyOwner
    {
        whitelistedContracts[contractAddress] = isWhitelisted;
    }

    /**
     * @dev Enables or disables the contract access whitelist.
     */
    function setWhitelistEnabled(bool isEnabled) external onlyOwner {
        isWhitelistEnabled = isEnabled;
    }

    /**
     * @dev Returns user slope at last user epoch.
     */
    function getLastUserSlope(address account) external view returns (int128) {
        uint256 userEpoch = userPointEpoch[account];
        return userPointHistory[account][userEpoch].slope;
    }

    /**
     * @dev Getter for user point history at point idx
     */
    function userPointHistoryTs(address account, uint256 idx)
        external
        view
        returns (uint256)
    {
        return userPointHistory[account][idx].ts;
    }

    /**
     * @dev Getter for account's locked position end time.
     */
    function lockedEnd(address account) external view returns (uint256) {
        return locked[account].end;
    }

    /**
     * @dev Updates the system state and optionally for a given account.
     */
    function _checkpoint(
        address account,
        LockedBalance memory oldLocked,
        LockedBalance memory newLocked
    ) private {
        Point memory uOld = EMPTY_POINT_FACTORY();
        Point memory uNew = EMPTY_POINT_FACTORY();
        int128 oldDslope;
        int128 newDslope;
        uint256 _epoch = epoch;

        if (account != address(0)) {
            // Calculate slopes and biases
            // kept at zero when they have to
            if (oldLocked.end > block.timestamp && oldLocked.amount > 0) {
                uOld.slope = oldLocked.amount / MAX_TIME;
                uOld.bias =
                    uOld.slope *
                    int128(int256(oldLocked.end) - int256(block.timestamp));
            }
            if (newLocked.end > block.timestamp && newLocked.amount > 0) {
                uNew.slope = newLocked.amount / MAX_TIME;
                uNew.bias =
                    uNew.slope *
                    int128(int256(newLocked.end) - int256(block.timestamp));
            }
            // Read values of schedules changes in the slope
            // oldLocked.end can be in the past and in the future
            // newLocked.end can ONLY be in the FUTURE unless everything
            // expired: then zeros
            oldDslope = slopeChanges[oldLocked.end];
            if (newLocked.end != 0) {
                if (newLocked.end == oldLocked.end) {
                    newDslope = oldDslope;
                } else {
                    newDslope = slopeChanges[newLocked.end];
                }
            }
        }

        Point memory lastPoint =
            Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number});

        if (epoch > 0) lastPoint = pointHistory[_epoch];
        uint256 lastCheckpoint = lastPoint.ts;

        // Used for extrapolation to calculate block number
        // (approximately, for *At methods) and save them
        // as it cannot be figured out exactly from inside the contract
        Point memory initialLastPoint = lastPoint;
        uint256 blockSlope;
        if (block.timestamp > lastPoint.ts)
            blockSlope =
                (MULTIPLIER * (block.number - lastPoint.blk)) /
                (block.timestamp - lastPoint.ts);

        // Round to nearest week.
        // Go over weeks to fill history and calculate what the current point
        // is.
        {
            uint256 t_i = (lastCheckpoint / WEEK) * WEEK;
            for (uint256 i = 0; i < 255; i++) {
                // If this does not get used in 5 years, users will be able to
                // withdraw but vote weight will be broken.
                t_i += WEEK;
                int128 dSlope;
                if (t_i > block.timestamp) {
                    t_i = block.timestamp;
                } else {
                    dSlope = slopeChanges[t_i];
                }
                lastPoint.bias -=
                    lastPoint.slope *
                    int128(int256(t_i) - int256(lastCheckpoint));
                lastPoint.slope += dSlope;
                // This can happen.
                if (lastPoint.bias < 0) {
                    lastPoint.bias = 0;
                }
                // In theory this cannot happen.
                if (lastPoint.slope < 0) {
                    lastPoint.slope = 0;
                }
                lastCheckpoint = t_i;
                lastPoint.ts = t_i;
                lastPoint.blk =
                    initialLastPoint.blk +
                    (blockSlope * (t_i - initialLastPoint.ts)) /
                    MULTIPLIER;
                _epoch += 1;
                if (t_i == block.timestamp) {
                    lastPoint.blk = block.number;
                    break;
                } else {
                    pointHistory[_epoch] = lastPoint;
                }
            }
        }
        epoch = _epoch;
        // pointHistory is now up to date with current block
        if (account != address(0)) {
            lastPoint.slope += (uNew.slope - uOld.slope);
            lastPoint.bias += (uNew.bias - uOld.bias);
            if (lastPoint.slope < 0) lastPoint.slope = 0;
            if (lastPoint.bias < 0) lastPoint.bias = 0;
        }
        // Record the changed point into history
        pointHistory[_epoch] = lastPoint;
        if (account != address(0)) {
            // Schedule the slope changes (slope is going down)
            // Subtract new slope from [newLocked.end]
            // Add old slope to [oldLocked.epoch]
            if (oldLocked.end > block.timestamp) {
                oldDslope += uOld.slope;
                if (newLocked.end == oldLocked.end) oldDslope -= uNew.slope; // new deposit, not extension
                slopeChanges[oldLocked.end] = oldDslope;
            }
            if (newLocked.end > block.timestamp) {
                if (newLocked.end > oldLocked.end) {
                    newDslope -= uNew.slope;
                    slopeChanges[newLocked.end] = newDslope;
                }
                // else: has already been recorded in oldDslope
            }
            // Handle user history
            uint256 userEpoch = userPointEpoch[account] + 1;
            userPointEpoch[account] = userEpoch;
            uNew.ts = block.timestamp;
            uNew.blk = block.number;
            userPointHistory[account][userEpoch] = Point({
                bias: uNew.bias,
                slope: uNew.slope,
                ts: block.timestamp,
                blk: block.number
            });
        }
    }

    /**
     * @dev Internal function to handle FOREX deposits and/or locktime increase.
     */
    function _depositFor(
        address account,
        uint256 value,
        uint256 unlockTime,
        LockedBalance memory lockedBalance,
        DepositType depositType
    ) private {
        require(!retiredContract, "Contract retired");
        LockedBalance memory _locked = lockedBalance;
        uint256 supplyBefore = supply;
        supply = supplyBefore + value;
        LockedBalance memory oldLocked =
            LockedBalance({amount: _locked.amount, end: _locked.end});
        // Adding to existing lock, or if expired create a new one
        _locked.amount += int128(int256(value));
        if (unlockTime != 0) _locked.end = unlockTime;
        locked[account] = _locked;
        // Possibilities:
        // both oldLocked.end could be current or expired (>/< block.timestamp)
        // value == 0 (extend lock) or value > 0 (add to lock or extend lock)
        // _locked.end > block.timestamp (always)
        _checkpoint(account, oldLocked, _locked);
        if (value != 0)
            IERC20(token).safeTransferFrom(account, address(this), value);
        // Stake into reward pool.
        uint256 stakeAmount =
            _locked.amount > 0 ? uint256(uint128(_locked.amount)) : 0;
        setUserRewardStakeAmount(account, stakeAmount);
        emit Deposit(account, value, _locked.end, depositType, block.timestamp);
        emit Supply(supplyBefore, supplyBefore + value);
    }

    /**
     * @dev Sets the staked value in the gFOREX reward pool for an account.
     */
    function setUserRewardStakeAmount(address account, uint256 value) private {
        if (retiredContract) return;
        (bool foundRewardPool, uint256 rewardPoolId) =
            rewardPool.getPoolIdByAlias(REWARD_POOL_ALIAS);
        if (!foundRewardPool) return;
        // Unstake current amount from pool.
        // TODO: check that the error return value is nonzero.
        rewardPool.unstake(account, 2**256 - 1, rewardPoolId);
        if (value > 0) {
            // Stake value.
            // TODO: check that the error return value is nonzero.
            rewardPool.stake(account, value, rewardPoolId);
        }
    }

    /**
     * @dev Updates the system state without affecting any
     *      specific account directly.
     */
    function checkpoint() external {
        LockedBalance memory empty;
        _checkpoint(
            address(0),
            EMPTY_LOCKED_BALANCE_FACTORY(),
            EMPTY_LOCKED_BALANCE_FACTORY()
        );
    }

    /**
     * @dev Increases the locked FOREX amount for an account by depositing more.
     */
    function depositFor(address account, uint256 value) external {
        LockedBalance storage _locked = locked[account];
        assert(value > 0);
        assert(_locked.amount > 0);
        assert(_locked.end > block.timestamp);
        _depositFor(account, value, 0, locked[account], DepositType.DepositFor);
    }

    /**
     * @dev Opens a new locked FOREX position for the message sender.
     */
    function createLock(uint256 value, uint256 unlockTime)
        external
        onlyAllowedLocker
    {
        // Round unlockTime to weeks.
        unlockTime = (unlockTime / WEEK) * WEEK;
        LockedBalance memory _locked = locked[msg.sender];
        assert(value > 0);
        require(_locked.amount == 0, "Withdraw old tokens first");
        require(unlockTime > block.timestamp, "Must unlock in the future");
        require(
            unlockTime <= block.timestamp + uint256(uint128(MAX_TIME)),
            "Lock must not be > 4 years"
        );
        _depositFor(
            msg.sender,
            value,
            unlockTime,
            _locked,
            DepositType.CreateLock
        );
    }

    /**
     * @dev Increases FOREX lock amount.
     */
    function increaseAmount(uint256 value) external onlyAllowedLocker {
        LockedBalance storage _locked = locked[msg.sender];
        assert(value > 0);
        require(_locked.amount > 0, "No existing lock");
        require(_locked.end > block.timestamp, "Lock has expired");
        _depositFor(
            msg.sender,
            value,
            0,
            _locked,
            DepositType.IncreaseLockAmount
        );
    }

    /**
     * @dev Increases FOREX lock time.
     */
    function increaseUnlockTime(uint256 unlockTime) external onlyAllowedLocker {
        LockedBalance storage _locked = locked[msg.sender];
        // Round unlockTime to weeks.
        unlockTime = (unlockTime / WEEK) * WEEK;
        require(_locked.end > block.timestamp, "Lock has expired");
        require(_locked.amount > 0, "Nothing is locked");
        require(unlockTime > _locked.end, "Can only increase lock duration");
        require(
            unlockTime <= block.timestamp + uint256(uint128(MAX_TIME)),
            "Lock must not be > 4 years"
        );
        _depositFor(
            msg.sender,
            0,
            unlockTime,
            _locked,
            DepositType.IncreaseUnlockTime
        );
    }

    /**
     * @dev Withdraws fully unlocked FOREX from contract as well as rewards.
     */
    function withdraw() external {
        LockedBalance storage _locked = locked[msg.sender];
        require(
            retiredContract || block.timestamp >= _locked.end,
            "The lock didn't expire"
        );
        require(_locked.amount > 0, "Nothing to withdraw");
        uint256 value = uint256(uint128(_locked.amount));
        LockedBalance memory oldLocked =
            LockedBalance({amount: _locked.amount, end: _locked.end});
        _locked.end = 0;
        _locked.amount = 0;
        uint256 supplyBefore = supply;
        supply = supplyBefore - value;
        if (!retiredContract) _checkpoint(msg.sender, oldLocked, _locked);
        IERC20(token).safeTransfer(msg.sender, value);
        // Unstake from reward pool.
        setUserRewardStakeAmount(msg.sender, 0);
        emit Withdraw(msg.sender, value, block.timestamp);
        emit Supply(supplyBefore, supplyBefore - value);
    }

    /**
     * @dev Finds epoch from block number and max epoch search range.
     */
    function findBlockEpoch(uint256 blockNumber, uint256 maxEpoch)
        private
        view
        returns (uint256 minEpoch)
    {
        minEpoch = 0;
        // Binary search for 128 bit value
        for (uint256 i = 0; i < 128; i++) {
            if (minEpoch >= maxEpoch) break;
            uint256 midEpoch = (minEpoch + maxEpoch + 1) / 2;
            if (pointHistory[midEpoch].blk <= blockNumber) {
                minEpoch = midEpoch;
            } else {
                maxEpoch = midEpoch - 1;
            }
        }
    }

    /**
     * @dev Returns an account's gFOREX balance.
     */
    function balanceOf(address account) public view returns (uint256) {
        uint256 epoch = userPointEpoch[account];
        if (epoch == 0) return 0;
        Point memory lastPoint = userPointHistory[account][epoch];
        lastPoint.bias -=
            lastPoint.slope *
            int128(int256(block.timestamp - lastPoint.ts));
        if (lastPoint.bias < 0) lastPoint.bias = 0;
        return uint256(uint128(lastPoint.bias));
    }

    /**
     * @dev Returns the gFOREX supply at time t.
     */
    function supplyAt(Point memory point, uint256 t)
        private
        view
        returns (uint256)
    {
        uint256 t_i = (point.ts / WEEK) * WEEK;
        for (uint256 i = 0; i < 255; i++) {
            t_i += WEEK;
            int128 dSlope;
            if (t_i > t) {
                t_i = t;
            } else {
                dSlope = slopeChanges[t_i];
            }
            point.bias -= point.slope * int128(int256(t_i) - int256(point.ts));
            if (t_i == t) break;
            point.slope += dSlope;
            point.ts = t_i;
        }
        if (point.bias < 0) point.bias = 0;
        return uint256(uint128(point.bias));
    }

    /**
     * @dev Returns the total gFOREX supply.
     */
    function totalSupply() external view returns (uint256) {
        uint256 _epoch = epoch;
        Point memory lastPoint = pointHistory[epoch];
        return supplyAt(lastPoint, block.timestamp);
    }

    /**
     * @dev Returns the total gFOREX supply at a block.
     * @param blockNumber The block to calculate the supply at.
     */
    function totalSupplyAt(uint256 blockNumber)
        external
        view
        returns (uint256)
    {
        require(blockNumber <= block.number);
        uint256 _epoch = epoch;
        uint256 targetEpoch = findBlockEpoch(blockNumber, _epoch);

        Point memory point = pointHistory[targetEpoch];
        uint256 dt = 0;

        if (targetEpoch < _epoch) {
            Point memory pointNext = pointHistory[targetEpoch + 1];
            if (point.blk != pointNext.blk) {
                dt =
                    ((blockNumber - point.blk) * (pointNext.ts - point.ts)) /
                    (pointNext.blk - point.blk);
            }
        } else if (point.blk != block.number) {
            dt =
                ((blockNumber - point.blk) * (block.timestamp - point.ts)) /
                (block.number - point.blk);
        }

        // Now, dt contains info on how far the current block is beyond "point".
        return supplyAt(point, point.ts + dt);
    }

    /**
     * @dev Measure balance of `account` at block height `blockNumber`
     * @param account Account to check balance from
     * @param blockNumber Block to calculate the balance at
     */
    function balanceOfAt(address account, uint256 blockNumber)
        external
        view
        returns (uint256)
    {
        // Copying and pasting totalSupply code because Vyper cannot pass by
        // reference yet
        require(blockNumber <= block.number);

        // Binary search
        uint256 _min = 0;
        uint256 _max = userPointEpoch[account];

        // Will be always enough for 128-bit numbers
        for (uint256 i = 0; i < 128; i++) {
            if (_min >= _max) {
                break;
            }
            uint256 _mid = (_min + _max + 1) / 2;
            if (userPointHistory[account][_mid].blk <= blockNumber) {
                _min = _mid;
            } else {
                _max = _mid - 1;
            }
        }

        Point memory upoint = userPointHistory[account][_min];
        uint256 max_epoch = epoch;
        uint256 _epoch = findBlockEpoch(blockNumber, max_epoch);
        Point memory point_0 = pointHistory[_epoch];
        uint256 d_block = 0;
        uint256 d_t = 0;

        if (_epoch < max_epoch) {
            Point memory point_1 = pointHistory[_epoch + 1];
            d_block = point_1.blk - point_0.blk;
            d_t = point_1.ts - point_0.ts;
        } else {
            d_block = block.number - point_0.blk;
            d_t = block.timestamp - point_0.ts;
        }

        uint256 block_time = point_0.ts;
        if (d_block != 0) {
            block_time += (d_t * (blockNumber - point_0.blk)) / d_block;
        }
        upoint.bias -=
            upoint.slope *
            (int128(uint128(block_time)) - int128(uint128(upoint.ts)));

        return upoint.bias >= 0 ? uint256(int256(upoint.bias)) : 0;
    }

    /**
     * @dev Returns whether addr is a contract (except for constructor).
     */
    function isContract(address addr) private returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(addr)
        }
        return size > 0;
    }

    function EMPTY_POINT_FACTORY() private view returns (Point memory) {
        return Point({bias: 0, slope: 0, ts: 0, blk: 0});
    }

    function EMPTY_LOCKED_BALANCE_FACTORY()
        private
        view
        returns (LockedBalance memory)
    {
        return LockedBalance({amount: 0, end: 0});
    }

    /** @dev Protected UUPS upgrade authorization fuction */
    function _authorizeUpgrade(address) internal override onlyOwner {}
}

File 11 of 28 : StakedErc20.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../interfaces/rewards/IRewardPool.sol";

/*                                                *\
 *                ,.-"""-.,                       *
 *               /   ===   \                      *
 *              /  =======  \                     *
 *           __|  (o)   (0)  |__                  *
 *          / _|    .---.    |_ \                 *
 *         | /.----/ O O \----.\ |                *
 *          \/     |     |     \/                 *
 *          |                   |                 *
 *          |                   |                 *
 *          |                   |                 *
 *          _\   -.,_____,.-   /_                 *
 *      ,.-"  "-.,_________,.-"  "-.,             *
 *     /          |       |  ╭-╮     \            *
 *    |           l.     .l  ┃ ┃      |           *
 *    |            |     |   ┃ ╰━━╮   |           *
 *    l.           |     |   ┃ ╭╮ ┃  .l           *
 *     |           l.   .l   ┃ ┃┃ ┃  | \,         *
 *     l.           |   |    ╰-╯╰-╯ .l   \,       *
 *      |           |   |           |      \,     *
 *      l.          |   |          .l        |    *
 *       |          |   |          |         |    *
 *       |          |---|          |         |    *
 *       |          |   |          |         |    *
 *       /"-.,__,.-"\   /"-.,__,.-"\"-.,_,.-"\    *
 *      |            \ /            |         |   *
 *      |             |             |         |   *
 *       \__|__|__|__/ \__|__|__|__/ \_|__|__/    *
\*                                                 */

/// @notice A receipt token for an ERC20 stake in the RewardPool.
contract StakedErc20 is ERC20, ReentrancyGuard {
    using SafeERC20 for ERC20;

    ERC20 public immutable depositToken;
    IRewardPool public immutable rewardPool;
    bytes32 public immutable rewardPoolAlias;

    modifier onlyRewardPool() {
        require(msg.sender == address(rewardPool), "StakedErc20: unauthorised");
        _;
    }

    constructor(
        string memory name_,
        string memory symbol_,
        address depositToken_,
        address rewardPool_,
        bytes32 rewardPoolAlias_
    ) ERC20(name_, symbol_) {
        require(
            depositToken_ != address(0) && rewardPool_ != address(0),
            "StakedErc20: invalid address"
        );
        require(depositToken_ != rewardPool_);
        require(depositToken_ != address(this));
        depositToken = ERC20(depositToken_);
        rewardPool = IRewardPool(rewardPool_);
        rewardPoolAlias = rewardPoolAlias_;
    }

    function decimals() public view override returns (uint8) {
        return depositToken.decimals();
    }

    /**
     * @dev Allows the RewardPool contract to mint tokens for stakers.
     * @param account The address to mint to.
     * @param amount The token amount to mint.
     */
    function mint(address account, uint256 amount) external onlyRewardPool {
        _validateMint(account, amount);
        _mint(account, amount);
    }

    /**
     * @dev Allows the RewardPool contract to burn tokens for stakers.
     * @param account The address to burn from.
     * @param amount The token amount to burn.
     */
    function burn(address account, uint256 amount) external onlyRewardPool {
        _validateBurn(account, amount);
        _burn(account, amount);
    }

    /**
     * @dev Overrides the _transfer function, unstaking the underlying
     *      assets from the sender and staking for the recipient.
     *      This ensures that all holders have a balance equal to
     *      their stake amount of the RewardPool contract.
     *      Rather than transferring, tokens are burned from the sender
     *      and minted to the receiver via the RewardPool.
     * @param sender The address of the token sender.
     * @param recipient The address of the token recipient.
     * @param amount The token amount being transferred.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override nonReentrant {
        uint256 poolId = _getRewardPoolId();
        // Unstake for the sender. The underlying token is
        // transferred into this contract.
        uint256 unstakeError = rewardPool.unstake(sender, amount, poolId);
        require(unstakeError == 0, "StakedErc20: failed to unstake sender");
        // Approve RewardPool to spend the underlying token.
        depositToken.safeApprove(address(rewardPool), amount);
        // Stake the underlying token for the receiver.
        uint256 stakeError = rewardPool.stake(recipient, amount, poolId);
        require(stakeError == 0, "StakedErc20: failed to stake receiver");
    }

    /**
     * @dev Validates the mint by checking that the resulting token
     *      balance for the user matches their deposit in RewardPool.
     * @param account The address to mint to.
     * @param amount The token amount to mint.
     */
    function _validateMint(address account, uint256 amount) private view {
        uint256 deposit = _getDepositAmount(account);
        uint256 futureBalance = balanceOf(account) + amount;
        require(deposit == futureBalance, "StakedErc20: mint is invalid");
    }

    /**
     * @dev Validates the burn by checking that the resulting token
     *      balance for the user matches their deposit in RewardPool.
     * @param account The address to burn from.
     * @param amount The token amount to burn.
     */
    function _validateBurn(address account, uint256 amount) private view {
        uint256 deposit = _getDepositAmount(account);
        uint256 futureBalance = balanceOf(account) - amount;
        require(deposit == futureBalance, "StakedErc20: burn is invalid");
    }

    /**
     * @dev Gets the deposit amount from the specified RewardPool.
     * @param account The user address to get the deposit for.
     */
    function _getDepositAmount(address account) private view returns (uint256) {
        uint256 poolId = _getRewardPoolId();
        return rewardPool.getDeposit(account, poolId).amount;
    }

    /**
     * @dev Gets the reward pool ID from the alias.
     */
    function _getRewardPoolId() private view returns (uint256) {
        (bool found, uint256 poolId) =
            rewardPool.getPoolIdByAlias(rewardPoolAlias);
        require(found, "StakedErc20: reward pool not found");
        return poolId;
    }
}

File 12 of 28 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @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, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * 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.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @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`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 13 of 28 : ERC1967UpgradeUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Upgrade.sol)

pragma solidity ^0.8.2;

import "../beacon/IBeaconUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/StorageSlotUpgradeable.sol";
import "../utils/Initializable.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 *
 * _Available since v4.1._
 *
 * @custom:oz-upgrades-unsafe-allow delegatecall
 */
abstract contract ERC1967UpgradeUpgradeable is Initializable {
    function __ERC1967Upgrade_init() internal onlyInitializing {
        __ERC1967Upgrade_init_unchained();
    }

    function __ERC1967Upgrade_init_unchained() internal onlyInitializing {
    }
    // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Returns the current implementation address.
     */
    function _getImplementation() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Perform implementation upgrade
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    /**
     * @dev Perform implementation upgrade with additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCall(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        _upgradeTo(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }
    }

    /**
     * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCallSecure(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        address oldImplementation = _getImplementation();

        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }

        // Perform rollback test if not already in progress
        StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
        if (!rollbackTesting.value) {
            // Trigger rollback using upgradeTo from the new implementation
            rollbackTesting.value = true;
            _functionDelegateCall(
                newImplementation,
                abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
            );
            rollbackTesting.value = false;
            // Check rollback was effective
            require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
            // Finally reset to the new implementation and log the upgrade
            _upgradeTo(newImplementation);
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Returns the current admin.
     */
    function _getAdmin() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        require(newAdmin != address(0), "ERC1967: new admin is the zero address");
        StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     */
    function _changeAdmin(address newAdmin) internal {
        emit AdminChanged(_getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
     */
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Emitted when the beacon is upgraded.
     */
    event BeaconUpgraded(address indexed beacon);

    /**
     * @dev Returns the current beacon.
     */
    function _getBeacon() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
        require(
            AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
            "ERC1967: beacon implementation is not a contract"
        );
        StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    }

    /**
     * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
     * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
     *
     * Emits a {BeaconUpgraded} event.
     */
    function _upgradeBeaconToAndCall(
        address newBeacon,
        bytes memory data,
        bool forceCall
    ) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
        }
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
        require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
    }
    uint256[50] private __gap;
}

File 14 of 28 : IBeaconUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeaconUpgradeable {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

File 15 of 28 : StorageSlotUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)

pragma solidity ^0.8.0;

/**
 * @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:
 * ```
 * 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(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
 */
library StorageSlotUpgradeable {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

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

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

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

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

File 16 of 28 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 17 of 28 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @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, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * 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.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @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`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // 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

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 18 of 28 : AccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
    function __AccessControl_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __AccessControl_init_unchained();
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role, _msgSender());
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        StringsUpgradeable.toHexString(uint160(account), 20),
                        " is missing role ",
                        StringsUpgradeable.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
    uint256[49] private __gap;
}

File 19 of 28 : IAccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

File 20 of 28 : ContextUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";

/**
 * @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 ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
        __Context_init_unchained();
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
    uint256[50] private __gap;
}

File 21 of 28 : StringsUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library StringsUpgradeable {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        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_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }
}

File 22 of 28 : ERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165Upgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
    function __ERC165_init() internal onlyInitializing {
        __ERC165_init_unchained();
    }

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165Upgradeable).interfaceId;
    }
    uint256[50] private __gap;
}

File 23 of 28 : IERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 24 of 28 : OwnableUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    address private _owner;

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

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

    function __Ownable_init_unchained() internal onlyInitializing {
        _transferOwnership(_msgSender());
    }

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

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _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);
    }
    uint256[49] private __gap;
}

File 25 of 28 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

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

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

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

File 26 of 28 : ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.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}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * 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.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

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

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * The default value of {decimals} is 18. To select a different value for
     * {decimals} you should overload it.
     *
     * 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 override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override 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 value {ERC20} uses, unless this function is
     * 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 override returns (uint8) {
        return 18;
    }

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

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

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

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

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        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}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        uint256 currentAllowance = _allowances[_msgSender()][spender];
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(_msgSender(), spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `sender` to `recipient`.
     *
     * 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.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[sender] = senderBalance - amount;
        }
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);

        _afterTokenTransfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` 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.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}

File 27 of 28 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
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);
}

File 28 of 28 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with 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;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"ignoreWhitelist","type":"bool"}],"name":"AllowNonWhitelistStakingActionChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"acount","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"poolIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"enum IRewardPool.AssetType","name":"assetType","type":"uint8"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"weight","type":"uint256"}],"name":"CreatePool","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"poolIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ForexDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ForexDistributedDirectly","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"aliasHash","type":"bytes32"}],"name":"PoolAliasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"ratePerSecond","type":"uint256"}],"name":"SetForexDistributionRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"fxTokens","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"SetFxTokenWeights","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"poolIds","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"SetPoolWeights","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Stake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"poolId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"whitelisted","type":"bool"}],"name":"WhitelistChanged","type":"event"},{"inputs":[],"name":"ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowNonWhitelistedStakingActions","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"claimFromPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"enum IRewardPool.AssetType","name":"assetType","type":"uint8"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"uint256[]","name":"poolIds","type":"uint256[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"createPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"distribute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"distributeDirectlyForPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"enabledPools","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"enabledPoolsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forex","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forexDistributionRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"getDeposit","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"S","type":"uint256"},{"internalType":"uint256","name":"boostWeight","type":"uint256"}],"internalType":"struct IRewardPool.Deposit","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getEnabledPoolsData","outputs":[{"internalType":"uint256[]","name":"poolRatios","type":"uint256[]"},{"internalType":"uint256[]","name":"accruedAmounts","type":"uint256[]"},{"internalType":"uint256[]","name":"deltaS","type":"uint256[]"},{"internalType":"uint256[]","name":"poolIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"category","type":"uint256"}],"name":"getFxTokenPoolAlias","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"getPool","outputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"enum IRewardPool.AssetType","name":"assetType","type":"uint8"},{"internalType":"address","name":"assetAddress","type":"address"},{"internalType":"uint256","name":"totalDeposits","type":"uint256"},{"internalType":"uint256","name":"S","type":"uint256"},{"internalType":"uint256","name":"totalRealDeposits","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"getPoolIdByAlias","outputs":[{"internalType":"bool","name":"found","type":"bool"},{"internalType":"uint256","name":"poolId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"totalRealDeposits","type":"uint256"},{"internalType":"uint256","name":"boostWeight","type":"uint256"}],"name":"getUserBoostedStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"handleAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastDistributionDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"poolBalanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"bool","name":"allow","type":"bool"}],"name":"setAllowNonWhitelistedStakingActions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"rate","type":"uint256"}],"name":"setForexDistributionRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_governanceLock","type":"address"}],"name":"setGovernanceLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_handle","type":"address"}],"name":"setHandleContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"setPoolAlias","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"poolIds","type":"uint256[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"setPools","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"poolId","type":"uint256"},{"internalType":"bool","name":"isWhitelisted","type":"bool"}],"name":"setWhitelistedStaker","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum IRewardPool.AssetType[]","name":"assetTypes","type":"uint8[]"},{"internalType":"address[]","name":"assetAddresses","type":"address[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"},{"internalType":"bytes32[]","name":"aliases","type":"bytes32[]"}],"name":"setupPools","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"stake","outputs":[{"internalType":"uint256","name":"errorCode","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"syncTotalRealDeposits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"poolId","type":"uint256"}],"name":"unstake","outputs":[{"internalType":"uint256","name":"errorCode","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"poolIds","type":"uint256[]"}],"name":"updateAccountBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"}]

60a06040523060601b60805234801561001757600080fd5b5060805160601c614a5161004b60003960008181610c7501528181610cb501528181610ed70152610f170152614a516000f3fe6080604052600436106102725760003560e01c8063889deaad1161014f578063a709e748116100c1578063e4cfffa61161007a578063e4cfffa61461081c578063e4fc6b6d1461084d578063e61ab44d14610862578063f525cb6814610882578063f5b541a614610899578063ffcd9da6146108bb57610272565b8063a709e74814610752578063b5ec189a14610769578063ce64c98614610780578063d547741f146107a5578063db86b799146107c5578063df483d8d146107fc57610272565b80639c301c99116101135780639c301c99146106a85780639c77153a146106c85780639e9d1d2f146106e8578063a217fddf14610708578063a2bc66be1461071d578063a6e81e6c1461073d57610272565b8063889deaad146106085780638a4962cd1461062857806391d1485414610648578063959fc4fd146106685780639b1c414f1461068857610272565b80634c46358f116101e85780636fbc6d19116101ac5780636fbc6d191461053e57806370a082311461055e57806374bf578b1461057e57806375b238fc146105b15780637a9744c7146105d35780638129fc1c146105f357610272565b80634c46358f146104745780634e71d92d146104945780634f1ef286146104a9578063541bf471146104bc5780636de6d5ec146104dc57610272565b80632726b5061161023a5780632726b5061461039c5780632f2ff15d146103de57806336568abe146103fe5780633659cfe61461041e57806339af71611461043e5780633b17c5171461045e57610272565b806301ffc9a714610277578063068bcd8d146102ac5780630c51b88f1461031c5780631710de071461034a578063248a9ca31461036c575b600080fd5b34801561028357600080fd5b5061029761029236600461431d565b6108dc565b60405190151581526020015b60405180910390f35b3480156102b857600080fd5b5061030a6102c73660046142b5565b600090815261012f602052604090208054600182015460048301546005840154600690940154929460ff8316946101009093046001600160a01b03169391929190565b6040516102a3969594939291906147c2565b34801561032857600080fd5b5061033c610337366004614108565b610915565b6040519081526020016102a3565b34801561035657600080fd5b5061036a6103653660046140c7565b610aed565b005b34801561037857600080fd5b5061033c6103873660046142b5565b600090815260c9602052604090206001015490565b3480156103a857600080fd5b506103bc6103b736600461409c565b610b4f565b60408051825181526020808401519082015291810151908201526060016102a3565b3480156103ea57600080fd5b5061036a6103f93660046142cd565b610bc5565b34801561040a57600080fd5b5061036a6104193660046142cd565b610bec565b34801561042a57600080fd5b5061036a610439366004613f76565b610c6a565b34801561044a57600080fd5b5061036a61045936600461435d565b610d33565b34801561046a57600080fd5b506101315461033c565b34801561048057600080fd5b5061036a61048f366004613f76565b610e3d565b3480156104a057600080fd5b5061036a610e94565b61036a6104b7366004613ffb565b610ecc565b3480156104c857600080fd5b5061033c6104d736600461440e565b610f82565b3480156104e857600080fd5b5061033c6104f736600461409c565b6040516bffffffffffffffffffffffff19606084901b1660208201526034810182905260009060540160405160208183030381529060405280519060200120905092915050565b34801561054a57600080fd5b5061036a610559366004613fae565b6110aa565b34801561056a57600080fd5b5061033c610579366004613f76565b6111d5565b34801561058a57600080fd5b5061012d546001600160a01b03165b6040516001600160a01b0390911681526020016102a3565b3480156105bd57600080fd5b5061033c6000805160206149fc83398151915281565b3480156105df57600080fd5b5061036a6105ee3660046142fc565b6112c6565b3480156105ff57600080fd5b5061036a6113f0565b34801561061457600080fd5b5061033c6106233660046142b5565b611516565b34801561063457600080fd5b5061036a610643366004614243565b611538565b34801561065457600080fd5b506102976106633660046142cd565b611594565b34801561067457600080fd5b5061033c61068336600461409c565b6115bf565b34801561069457600080fd5b5061036a6106a3366004613f76565b61161a565b3480156106b457600080fd5b5061036a6106c336600461413c565b611702565b3480156106d457600080fd5b5061036a6106e33660046142b5565b611956565b3480156106f457600080fd5b5061036a6107033660046142b5565b611aa0565b34801561071457600080fd5b5061033c600081565b34801561072957600080fd5b5061033c610738366004614108565b611b70565b34801561074957600080fd5b5061036a611cb8565b34801561075e57600080fd5b5061033c6101335481565b34801561077557600080fd5b5061033c6101325481565b34801561078c57600080fd5b50610795611d37565b6040516102a3949392919061457d565b3480156107b157600080fd5b5061036a6107c03660046142cd565b61217d565b3480156107d157600080fd5b506107e56107e03660046142b5565b6121a3565b6040805192151583526020830191909152016102a3565b34801561080857600080fd5b5061036a610817366004614381565b6121de565b34801561082857600080fd5b506102976108373660046142b5565b6101366020526000908152604090205460ff1681565b34801561085957600080fd5b5061036a612287565b34801561086e57600080fd5b5061036a61087d3660046142fc565b61245b565b34801561088e57600080fd5b5061033c6101345481565b3480156108a557600080fd5b5061033c6000805160206149b583398151915281565b3480156108c757600080fd5b5061012e54610599906001600160a01b031681565b60006001600160e01b03198216637965db0b60e01b148061090d57506301ffc9a760e01b6001600160e01b03198316145b90505b919050565b6000600260fb5414156109435760405162461bcd60e51b815260040161093a90614718565b60405180910390fd5b600260fb55600082815261012f602052604090206109618584612509565b6109a85760405162461bcd60e51b815260206004820152601860248201527714995dd85c99141bdbdb0e881d5b985d5d1a1bdc9a5cd95960421b604482015260640161093a565b6000600182015460ff1660028111156109d157634e487b7160e01b600052602160045260246000fd5b1480610a02575060018082015460ff166002811115610a0057634e487b7160e01b600052602160045260246000fd5b145b610a4e5760405162461bcd60e51b815260206004820152601d60248201527f526577617264506f6f6c3a206e6f742079657420737570706f72746564000000604482015260640161093a565b610a5785612569565b6001600160a01b038516600090815260038201602052604090208054610a8a908790610a84908890614899565b866126f4565b610a96868684846128f4565b60408051858152602081018790526001600160a01b038816917f5af417134f72a9d41143ace85b0a26dce6f550f894f2cbc1eeee8810603d91b6910160405180910390a26000925050505b600160fb559392505050565b610b056000805160206149b583398151915233611594565b80610b235750610b236000805160206149fc83398151915233611594565b610b3f5760405162461bcd60e51b815260040161093a90614649565b610b4a83838361293c565b505050565b610b7360405180606001604052806000815260200160008152602001600081525090565b50600081815261012f602090815260408083206001600160a01b038616845260030182529182902082516060810184528154815260018201549281019290925260020154918101919091525b92915050565b600082815260c96020526040902060010154610be281335b612a24565b610b4a8383612a88565b6001600160a01b0381163314610c5c5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b606482015260840161093a565b610c668282612b0e565b5050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610cb35760405162461bcd60e51b815260040161093a906145fd565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610ce5612b75565b6001600160a01b031614610d0b5760405162461bcd60e51b815260040161093a90614665565b610d1481612ba3565b60408051600080825260208201909252610d3091839190612bd7565b50565b610d4b6000805160206149b583398151915233611594565b80610d695750610d696000805160206149fc83398151915233611594565b610d855760405162461bcd60e51b815260040161093a90614649565b6000828152610136602052604090205460ff1615158115151415610de55760405162461bcd60e51b815260206004820152601760248201527614995dd85c99141bdbdb0e88185b1c9958591e481cd95d604a1b604482015260640161093a565b60008281526101366020908152604091829020805460ff1916841515908117909155915191825283917fca941afc9b909115ae8e7e71562ecfcfb764d82e8ee1f19607f384b76851b58e910160405180910390a25050565b610e556000805160206149fc83398151915233611594565b610e715760405162461bcd60e51b815260040161093a906146b1565b61013580546001600160a01b0319166001600160a01b0392909216919091179055565b600260fb541415610eb75760405162461bcd60e51b815260040161093a90614718565b600260fb55610ec533612569565b600160fb55565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610f155760405162461bcd60e51b815260040161093a906145fd565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610f47612b75565b6001600160a01b031614610f6d5760405162461bcd60e51b815260040161093a90614665565b610f7682612ba3565b610c6682826001612bd7565b610135546000906001600160a01b03161580610f9c575083155b15610fa85750826110a3565b61013554604080516318160ddd60e01b815290516000926001600160a01b0316916318160ddd916004808301926020929190829003018186803b158015610fee57600080fd5b505afa158015611002573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110269190614345565b90508061103657849150506110a3565b600061104382600a6148d1565b61104d85876148d1565b61105890600f6148d1565b61106291906148b1565b905060006110708288614899565b90506000600a6110818960196148d1565b61108b91906148b1565b905080821161109a578161109c565b805b9450505050505b9392505050565b805160005b818110156111cf5760008382815181106110d957634e487b7160e01b600052603260045260246000fd5b60200260200101519050600061012f6000838152602001908152602001600020905060008160040154116111465760405162461bcd60e51b8152602060048201526014602482015273141bdbdb081b9bdd081a5b9a5d1a585b1a5cd95960621b604482015260640161093a565b6001600160a01b0386166000908152600382016020526040902054806111ae5760405162461bcd60e51b815260206004820152601960248201527f4163636f756e7420686173206e6f74206465706f736974656400000000000000604482015260640161093a565b6111b98782856126f4565b50505080806111c79061494a565b9150506110af565b50505050565b6000806111e0611d37565b50610131549093509150600090505b818110156112be576000610131828154811061121b57634e487b7160e01b600052603260045260246000fd5b600091825260208083209091015480835261012f825260408084206001600160a01b038b168552600381019093528320875191945091929087908690811061127357634e487b7160e01b600052603260045260246000fd5b6020026020010151836005015461128a9190614899565b905061129b81846006015484612d22565b6112a59089614899565b97505050505080806112b69061494a565b9150506111ef565b505050919050565b6112de6000805160206149b583398151915233611594565b806112fc57506112fc6000805160206149fc83398151915233611594565b6113185760405162461bcd60e51b815260040161093a90614649565b600082815261012f6020526040902060048101546113785760405162461bcd60e51b815260206004820152601960248201527f526577617264506f6f6c3a20706f6f6c20697320656d70747900000000000000604482015260640161093a565b600481015461138f83670de0b6b3a76400006148d1565b61139991906148b1565b8160050160008282546113ac9190614899565b909155505060408051848152602081018490527f210c94e3f030a6ae22b3965107af4246007dedf4ccbd2867ab9b5b5e30c7a2c791015b60405180910390a1505050565b600054610100900460ff1661140b5760005460ff161561140f565b303b155b6114725760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161093a565b600054610100900460ff16158015611494576000805461ffff19166101011790555b61149c612d96565b6114a4612dcd565b6114ac612dfc565b6114c46000805160206149fc83398151915233612e2b565b6114ea6000805160206149b58339815191526000805160206149fc833981519152612e35565b6115026000805160206149fc83398151915280612e35565b8015610d30576000805461ff001916905550565b610131818154811061152757600080fd5b600091825260209091200154905081565b6115506000805160206149b583398151915233611594565b8061156e575061156e6000805160206149fc83398151915233611594565b61158a5760405162461bcd60e51b815260040161093a90614649565b610c668282612e80565b600091825260c9602090815260408084206001600160a01b0393909316845291905290205460ff1690565b600081815261012f602090815260408083206001600160a01b0386168452600381019092528220826115f083613048565b83600501546115ff9190614899565b905061161081846006015484612d22565b9695505050505050565b6116326000805160206149fc83398151915233611594565b61164e5760405162461bcd60e51b815260040161093a906146b1565b61012d80546001600160a01b0319166001600160a01b0383169081179091556040805160016219312d60e11b03198152905163ffcd9da691600480820192602092909190829003018186803b1580156116a657600080fd5b505afa1580156116ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116de9190613f92565b61012e80546001600160a01b0319166001600160a01b039290921691909117905550565b61171a6000805160206149b583398151915233611594565b8061173857506117386000805160206149fc83398151915233611594565b6117545760405162461bcd60e51b815260040161093a90614649565b61013454156117a55760405162461bcd60e51b815260206004820152601960248201527f506f6f6c7320616c726561647920696e697469616c6973656400000000000000604482015260640161093a565b83518351811480156117b75750825181145b6117d157634e487b7160e01b600052600160045260246000fd5b6000816001600160401b038111156117f957634e487b7160e01b600052604160045260246000fd5b604051908082528060200260200182016040528015611822578160200160208202803683370190505b50905060005b8281101561193557600081815261012f602052604090208251829084908290811061186357634e487b7160e01b600052603260045260246000fd5b6020026020010181815250506118f18189848151811061189357634e487b7160e01b600052603260045260246000fd5b60200260200101518985815181106118bb57634e487b7160e01b600052603260045260246000fd5b60200260200101518986815181106118e357634e487b7160e01b600052603260045260246000fd5b60200260200101518661310d565b61192285838151811061191457634e487b7160e01b600052603260045260246000fd5b60200260200101518361245b565b508061192d8161494a565b915050611828565b50805161194a90610131906020840190613e32565b50506101345550505050565b600260fb5414156119795760405162461bcd60e51b815260040161093a90614718565b600260fb55600061198a33836131bd565b9050806119975750611a98565b60408051600180825281830190925260009160208083019080368337505060408051600180825281830190925292935060009291506020808301908036833701905050905083826000815181106119fe57634e487b7160e01b600052603260045260246000fd5b6020026020010181815250508281600081518110611a2c57634e487b7160e01b600052603260045260246000fd5b602090810291909101015261012e54611a4f906001600160a01b03163385613266565b336001600160a01b03167f274a65b8c8fd25732fc33bc5a16dd4e85d44fedfdf10caa13c268380038ddaf6848484604051611a8c9392919061474f565b60405180910390a25050505b50600160fb55565b611ab86000805160206149b583398151915233611594565b80611ad65750611ad66000805160206149fc83398151915233611594565b611af25760405162461bcd60e51b815260040161093a90614649565b61013254811415611b0257610d30565b426101335414611b3457611b14612287565b426101335414611b3457634e487b7160e01b600052600160045260246000fd5b6101328190556040518181527fc5763479bd9bf9105881647ea9226f99c26cb53d6df3a140b6a1f3d1128841799060200160405180910390a150565b6000600260fb541415611b955760405162461bcd60e51b815260040161093a90614718565b600260fb55600082815261012f60205260409020611bb38584612509565b611bfa5760405162461bcd60e51b815260206004820152601860248201527714995dd85c99141bdbdb0e881d5b985d5d1a1bdc9a5cd95960421b604482015260640161093a565b6001600160a01b038516600090815260038201602052604090208054611c2557600192505050610ae1565b611c2e86612569565b805480861115611c3c578095505b6000611c4887836148f0565b9050611c558882886126f4565b611c61888886866132c9565b60408051878152602081018990526001600160a01b038a16917ff960dbf9e5d0682f7a298ed974e33a28b4464914b7a2bfac12ae419a9afeb280910160405180910390a25050600160fb5550600095945050505050565b611cd06000805160206149fc83398151915233611594565b611cec5760405162461bcd60e51b815260040161093a906146b1565b6101345460005b81811015610c6657600081815261012f602052604090206004810154611d195750611d25565b60048101546006909101555b80611d2f8161494a565b915050611cf3565b61013154606090819081908190806001600160401b03811115611d6a57634e487b7160e01b600052604160045260246000fd5b604051908082528060200260200182016040528015611d93578160200160208202803683370190505b509450806001600160401b03811115611dbc57634e487b7160e01b600052604160045260246000fd5b604051908082528060200260200182016040528015611de5578160200160208202803683370190505b509350806001600160401b03811115611e0e57634e487b7160e01b600052604160045260246000fd5b604051908082528060200260200182016040528015611e37578160200160208202803683370190505b509250806001600160401b03811115611e6057634e487b7160e01b600052604160045260246000fd5b604051908082528060200260200182016040528015611e89578160200160208202803683370190505b50915080611e975750612177565b6000611ea1613311565b905060005b82811015611fbf576101318181548110611ed057634e487b7160e01b600052603260045260246000fd5b9060005260206000200154848281518110611efb57634e487b7160e01b600052603260045260246000fd5b602002602001018181525050600061012f6000868481518110611f2e57634e487b7160e01b600052603260045260246000fd5b6020026020010151815260200190815260200160002090508260001480611f5457508054155b15611f5f5750611fad565b80548390611f7590670de0b6b3a76400006148d1565b611f7f91906148b1565b888381518110611f9f57634e487b7160e01b600052603260045260246000fd5b602002602001018181525050505b80611fb78161494a565b915050611ea6565b50600061013354421480611fd4575061013254155b90508015611fe457505050612177565b6101325461013354600090611ff990426148f0565b9050600061200783836148d1565b905060005b8681101561216f57600061012f60008a848151811061203b57634e487b7160e01b600052603260045260246000fd5b60200260200101518152602001908152602001600020905080600001546000148061206857506004810154155b15612073575061215d565b670de0b6b3a76400008c838151811061209c57634e487b7160e01b600052603260045260246000fd5b6020026020010151846120af91906148d1565b6120b991906148b1565b8b83815181106120d957634e487b7160e01b600052603260045260246000fd5b60200260200101818152505080600401548b838151811061210a57634e487b7160e01b600052603260045260246000fd5b6020026020010151670de0b6b3a764000061212591906148d1565b61212f91906148b1565b8a838151811061214f57634e487b7160e01b600052603260045260246000fd5b602002602001018181525050505b806121678161494a565b91505061200c565b505050505050505b90919293565b600082815260c960205260409020600101546121998133610bdd565b610b4a8383612b0e565b600081815261013060205260408120548190806121c75760008092509250506121d9565b60016121d381836148f0565b92509250505b915091565b6121f66000805160206149b583398151915233611594565b8061221457506122146000805160206149fc83398151915233611594565b6122305760405162461bcd60e51b815260040161093a90614649565b61223a8282612e80565b61013480546000918261224c8361494a565b90915550600081815261012f6020526040902090915061226f8187878a8661310d565b861561227e5761227e8261338a565b50505050505050565b6101335442141561229757612459565b6000806122a2611d37565b509250925050600061013354426122b991906148f0565b426101335583519091506122cf57505050612459565b81518351146122ee57634e487b7160e01b600052600160045260246000fd5b61013154600090815b8181101561240e57600061012f6000610131848154811061232857634e487b7160e01b600052603260045260246000fd5b90600052602060002001548152602001908152602001600020905086828151811061236357634e487b7160e01b600052603260045260246000fd5b60200260200101516000148061237857508054155b1561238357506123fc565b8582815181106123a357634e487b7160e01b600052603260045260246000fd5b60200260200101518160050160008282546123be9190614899565b925050819055508682815181106123e557634e487b7160e01b600052603260045260246000fd5b6020026020010151846123f89190614899565b9350505b806124068161494a565b9150506122f7565b507f19ce097e53a1fc72f05d4aeb15d0a311003530e7071c94493a22b71e07352e088361013254846101318960405161244b959493929190614803565b60405180910390a150505050505b565b6124736000805160206149b583398151915233611594565b8061249157506124916000805160206149fc83398151915233611594565b6124ad5760405162461bcd60e51b815260040161093a90614649565b6124b8816001614899565b600083815261013060209081526040918290209290925580518381529182018490527f0bd70adcb4a4ff93bb3cfd3a095c56f26903685009797109f8bab78ff9d57f45910160405180910390a15050565b600081815261012f6020908152604080832033845260020190915281205460ff16801561253a576001915050610bbf565b6001600160a01b0384163314801561256157506000838152610136602052604090205460ff165b949350505050565b612571612287565b42610133541461259157634e487b7160e01b600052600160045260246000fd5b610131546000816001600160401b038111156125bd57634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156125e6578160200160208202803683370190505b5090506000805b83811015612681576000610131828154811061261957634e487b7160e01b600052603260045260246000fd5b90600052602060002001549050600061263287836131bd565b90508085848151811061265557634e487b7160e01b600052603260045260246000fd5b602090810291909101015261266a8185614899565b9350505080806126799061494a565b9150506125ed565b508061268f57505050610d30565b61012e546126a7906001600160a01b03168583613266565b836001600160a01b03167f274a65b8c8fd25732fc33bc5a16dd4e85d44fedfdf10caa13c268380038ddaf682610131856040516126e69392919061477a565b60405180910390a250505050565b600081815261012f602090815260408083206001600160a01b038716845260038101909252822080546006830154600283015493949293612736929190610f82565b610135549091506000906001600160a01b03166127545760006127d2565b610135546040516370a0823160e01b81526001600160a01b038981166004830152909116906370a082319060240160206040518083038186803b15801561279a57600080fd5b505afa1580156127ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127d29190614345565b6002840181905590508561283c578184600401546127f091906148f0565b60048501558254600685015461280691906148f0565b60068501555050506001600160a01b03841660009081526003909101602052604081208181556001810182905560020155610b4a565b600584015460018401558254861061286e57825461285a90876148f0565b84600601546128699190614899565b61288a565b825461287b9087906148f0565b846006015461288a91906148f0565b600685018190556000906128a090889084610f82565b90508083116128c7576128b383826148f0565b85600401546128c29190614899565b6128e0565b6128d181846148f0565b85600401546128e091906148f0565b600490950194909455505083905550505050565b60018083015460ff16600281111561291c57634e487b7160e01b600052602160045260246000fd5b14612926576111cf565b6129308383613471565b6111cf84848484613544565b600082815261012f602090815260408083206001600160a01b038716845260020190915290205460ff16151581151514156129b35760405162461bcd60e51b815260206004820152601760248201527614995dd85c99141bdbdb0e88185b1c9958591e481cd95d604a1b604482015260640161093a565b600082815261012f602090815260408083206001600160a01b0387168085526002909101835292819020805460ff191685151590811790915581519384529183018590528201527fa839f18792cf90163c4f23341b425ae8ed1de428169e4006c33f71a023ee6c29906060016113e3565b612a2e8282611594565b610c6657612a46816001600160a01b0316601461364b565b612a5183602061364b565b604051602001612a629291906144e3565b60408051601f198184030181529082905262461bcd60e51b825261093a916004016145ca565b612a928282611594565b610c6657600082815260c9602090815260408083206001600160a01b03851684529091529020805460ff19166001179055612aca3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b612b188282611594565b15610c6657600082815260c9602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b612bbb6000805160206149fc83398151915233611594565b610d305760405162461bcd60e51b815260040161093a906146b1565b6000612be1612b75565b9050612bec8461382c565b600083511180612bf95750815b15612c0a57612c0884846138d1565b505b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143805460ff16612d1b57805460ff191660011781556040516001600160a01b0383166024820152612c8990869060440160408051601f198184030181529190526020810180516001600160e01b0316631b2ce7f360e11b1790526138d1565b50805460ff19168155612c9a612b75565b6001600160a01b0316826001600160a01b031614612d125760405162461bcd60e51b815260206004820152602f60248201527f45524331393637557067726164653a207570677261646520627265616b73206660448201526e75727468657220757067726164657360881b606482015260840161093a565b612d1b856139bc565b5050505050565b600080826001015490506000612d418460000154868660020154610f82565b9050801580612d505750858210155b15612d60576000925050506110a3565b6000612d6c83886148f0565b9050670de0b6b3a7640000612d8182846148d1565b612d8b91906148b1565b979650505050505050565b600054610100900460ff16612dbd5760405162461bcd60e51b815260040161093a906146cd565b612dc56139fc565b6124596139fc565b600054610100900460ff16612df45760405162461bcd60e51b815260040161093a906146cd565b612dbd6139fc565b600054610100900460ff16612e235760405162461bcd60e51b815260040161093a906146cd565b612459613a23565b610c668282612a88565b600082815260c96020526040808220600101805490849055905190918391839186917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a4505050565b815181518114612ed25760405162461bcd60e51b815260206004820181905260248201527f526577617264506f6f6c3a2061726773206c656e677468206d69736d61746368604482015260640161093a565b6101345460005b81811015612ff357600080805b85811015612f3f57878181518110612f0e57634e487b7160e01b600052603260045260246000fd5b60200260200101518414612f2157612f2d565b60019250809150612f3f565b80612f378161494a565b915050612ee6565b50600083815261012f6020526040902082612f60576000905550612fe19050565b6000878381518110612f8257634e487b7160e01b600052603260045260246000fd5b6020026020010151905060008111612fdc5760405162461bcd60e51b815260206004820181905260248201527f526577617264506f6f6c3a20736574207a65726f2d77656967687420706f6f6c604482015260640161093a565b905550505b80612feb8161494a565b915050612ed9565b50835161300890610131906020870190613e32565b507fcabae56731cea70615176c847ec16408fd97d7eb3cc387917aea0d7de26f4597848460405161303a929190614558565b60405180910390a150505050565b600080613053613311565b6004840154909150811580613066575080155b1561307657600092505050610910565b610132546101335460009061308b90426148f0565b9050600061309983836148d1565b90506000858860000154670de0b6b3a76400006130b691906148d1565b6130c091906148b1565b90506000670de0b6b3a76400006130d783856148d1565b6130e191906148b1565b9050856130f682670de0b6b3a76400006148d1565b61310091906148b1565b9998505050505050505050565b6131178484613a4a565b60018086018054869260ff199091169083600281111561314757634e487b7160e01b600052602160045260246000fd5b0217905550600185018054610100600160a81b0319166101006001600160a01b0386169081029190911790915582865515613188576131888382600161293c565b7ff37d8e8567f4d54f174bbf67f5f838918fdd376d4777566b7337eeb88a1ac93c8185858560405161244b9493929190614793565b600081815261012f602090815260408083206001600160a01b03861684526003810190925282206001810154600583015460028301548354613201908990896126f4565b81831061321657600095505050505050610bbf565b600061322b8560000154876006015484610f82565b9050600061323985856148f0565b9050670de0b6b3a764000061324e82846148d1565b61325891906148b1565b9a9950505050505050505050565b6040516001600160a01b038316602482015260448101829052610b4a90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152613b13565b60018083015460ff1660028111156132f157634e487b7160e01b600052602160045260246000fd5b146132fb576111cf565b6133058383613be5565b6111cf84848484613cb7565b61013154600090815b818110156133855761012f6000610131838154811061334957634e487b7160e01b600052603260045260246000fd5b9060005260206000200154815260200190815260200160002060000154836133719190614899565b92508061337d8161494a565b91505061331a565b505090565b6101315460005b81811015613439578261013182815481106133bc57634e487b7160e01b600052603260045260246000fd5b9060005260206000200154146133d157613427565b60405162461bcd60e51b815260206004820152602560248201527f526577617264506f6f6c3a2070757368696e67206475706c696361746520706f6044820152641bdb08125160da1b606482015260840161093a565b806134318161494a565b915050613391565b505061013180546001810182556000919091527fbd987ad6ccdb7c7567f7335ea839f95dc944431abcf935b6924f70215963db730155565b600181015461010090046001600160a01b031661349e57634e487b7160e01b600052600160045260246000fd5b60008160010160019054906101000a90046001600160a01b031690506000816001600160a01b031663c89039c56040518163ffffffff1660e01b815260040160206040518083038186803b1580156134f557600080fd5b505afa158015613509573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061352d9190613f92565b90506111cf6001600160a01b038216333087613cfa565b60018201546040516340c10f1960e01b81526001600160a01b038681166004830152602482018690526101009092049091169081906340c10f19906044015b600060405180830381600087803b15801561359d57600080fd5b505af11580156135b1573d6000803e3d6000fd5b50506040516370a0823160e01b81526001600160a01b038881166004830152841692506370a08231915060240160206040518083038186803b1580156135f657600080fd5b505afa15801561360a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061362e9190614345565b825414612d1b57634e487b7160e01b600052600160045260246000fd5b6060600061365a8360026148d1565b613665906002614899565b6001600160401b0381111561368a57634e487b7160e01b600052604160045260246000fd5b6040519080825280601f01601f1916602001820160405280156136b4576020820181803683370190505b509050600360fc1b816000815181106136dd57634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061371a57634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a905350600061373e8460026148d1565b613749906001614899565b90505b60018111156137dd576f181899199a1a9b1b9c1cb0b131b232b360811b85600f166010811061378b57634e487b7160e01b600052603260045260246000fd5b1a60f81b8282815181106137af57634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535060049490941c936137d681614933565b905061374c565b5083156110a35760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e74604482015260640161093a565b803b6138905760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161093a565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060823b6139305760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b606482015260840161093a565b600080846001600160a01b03168460405161394b91906144c7565b600060405180830381855af49150503d8060008114613986576040519150601f19603f3d011682016040523d82523d6000602084013e61398b565b606091505b50915091506139b382826040518060600160405280602781526020016149d560279139613d32565b95945050505050565b6139c58161382c565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b600054610100900460ff166124595760405162461bcd60e51b815260040161093a906146cd565b600054610100900460ff16610ec55760405162461bcd60e51b815260040161093a906146cd565b600080836002811115613a6d57634e487b7160e01b600052602160045260246000fd5b148015613a8157506001600160a01b038216155b9050600080846002811115613aa657634e487b7160e01b600052602160045260246000fd5b14158015613abc57506001600160a01b03831615155b90508180613ac75750805b6111cf5760405162461bcd60e51b815260206004820181905260248201527f526577617264506f6f6c3a20696e76616c696420747970652f61646472657373604482015260640161093a565b6000613b68826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316613d6b9092919063ffffffff16565b805190915015610b4a5780806020019051810190613b869190614299565b610b4a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161093a565b600181015461010090046001600160a01b0316613c1257634e487b7160e01b600052600160045260246000fd5b60008160010160019054906101000a90046001600160a01b031690506000816001600160a01b031663c89039c56040518163ffffffff1660e01b815260040160206040518083038186803b158015613c6957600080fd5b505afa158015613c7d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613ca19190613f92565b90506111cf6001600160a01b0382163386613266565b6001820154604051632770a7eb60e21b81526001600160a01b03868116600483015260248201869052610100909204909116908190639dc29fac90604401613583565b6040516001600160a01b03808516602483015283166044820152606481018290526111cf9085906323b872dd60e01b90608401613292565b60608315613d415750816110a3565b825115613d515782518084602001fd5b8160405162461bcd60e51b815260040161093a91906145ca565b6060612561848460008585843b613dc45760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161093a565b600080866001600160a01b03168587604051613de091906144c7565b60006040518083038185875af1925050503d8060008114613e1d576040519150601f19603f3d011682016040523d82523d6000602084013e613e22565b606091505b5091509150612d8b828286613d32565b828054828255906000526020600020908101928215613e6d579160200282015b82811115613e6d578251825591602001919060010190613e52565b50613e79929150613e7d565b5090565b5b80821115613e795760008155600101613e7e565b600082601f830112613ea2578081fd5b81356020613eb7613eb283614876565b614846565b80838252828201915082860187848660051b8901011115613ed6578586fd5b855b85811015613efd578135613eeb81614991565b84529284019290840190600101613ed8565b5090979650505050505050565b600082601f830112613f1a578081fd5b81356020613f2a613eb283614876565b80838252828201915082860187848660051b8901011115613f49578586fd5b855b85811015613efd57813584529284019290840190600101613f4b565b80356003811061091057600080fd5b600060208284031215613f87578081fd5b81356110a381614991565b600060208284031215613fa3578081fd5b81516110a381614991565b60008060408385031215613fc0578081fd5b8235613fcb81614991565b915060208301356001600160401b03811115613fe5578182fd5b613ff185828601613f0a565b9150509250929050565b6000806040838503121561400d578182fd5b823561401881614991565b91506020838101356001600160401b0380821115614034578384fd5b818601915086601f830112614047578384fd5b8135818111156140595761405961497b565b61406b601f8201601f19168501614846565b91508082528784828501011115614080578485fd5b8084840185840137810190920192909252919491935090915050565b600080604083850312156140ae578182fd5b82356140b981614991565b946020939093013593505050565b6000806000606084860312156140db578081fd5b83356140e681614991565b92506020840135915060408401356140fd816149a6565b809150509250925092565b60008060006060848603121561411c578081fd5b833561412781614991565b95602085013595506040909401359392505050565b60008060008060808587031215614151578182fd5b84356001600160401b0380821115614167578384fd5b818701915087601f83011261417a578384fd5b8135602061418a613eb283614876565b8083825282820191508286018c848660051b89010111156141a9578889fd5b8896505b848710156141d2576141be81613f67565b8352600196909601959183019183016141ad565b50985050880135925050808211156141e8578384fd5b6141f488838901613e92565b94506040870135915080821115614209578384fd5b61421588838901613f0a565b9350606087013591508082111561422a578283fd5b5061423787828801613f0a565b91505092959194509250565b60008060408385031215614255578182fd5b82356001600160401b038082111561426b578384fd5b61427786838701613f0a565b9350602085013591508082111561428c578283fd5b50613ff185828601613f0a565b6000602082840312156142aa578081fd5b81516110a3816149a6565b6000602082840312156142c6578081fd5b5035919050565b600080604083850312156142df578182fd5b8235915060208301356142f181614991565b809150509250929050565b6000806040838503121561430e578182fd5b50508035926020909101359150565b60006020828403121561432e578081fd5b81356001600160e01b0319811681146110a3578182fd5b600060208284031215614356578081fd5b5051919050565b6000806040838503121561436f578182fd5b8235915060208301356142f1816149a6565b600080600080600060a08688031215614398578283fd5b853594506143a860208701613f67565b935060408601356143b881614991565b925060608601356001600160401b03808211156143d3578283fd5b6143df89838a01613f0a565b935060808801359150808211156143f4578283fd5b5061440188828901613f0a565b9150509295509295909350565b600080600060608486031215614422578081fd5b505081359360208301359350604090920135919050565b6000815180845260208085019450808401835b838110156144685781518752958201959082019060010161444c565b509495945050505050565b6000815480845260208085019450838352808320835b8381101561446857815487529582019560019182019101614489565b600381106144c357634e487b7160e01b600052602160045260246000fd5b9052565b600082516144d9818460208701614907565b9190910192915050565b60007f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008252835161451b816017850160208801614907565b7001034b99036b4b9b9b4b733903937b6329607d1b601791840191820152835161454c816028840160208801614907565b01602801949350505050565b60006040825261456b6040830185614439565b82810360208401526139b38185614439565b6000608082526145906080830187614439565b82810360208401526145a28187614439565b905082810360408401526145b68186614439565b90508281036060840152612d8b8185614439565b60006020825282518060208401526145e9816040850160208701614907565b601f01601f19169190910160400192915050565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252600290820152614e5760f01b604082015260600190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252600290820152614e4160f01b604082015260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b6000848252606060208301526147686060830185614439565b82810360408401526116108185614439565b6000848252606060208301526147686060830185614473565b848152608081016147a760208301866144a5565b6001600160a01b039390931660408201526060015292915050565b86815260c081016147d660208301886144a5565b6001600160a01b039590951660408201526060810193909352608083019190915260a09091015292915050565b600086825285602083015284604083015260a0606083015261482860a0830185614473565b828103608084015261483a8185614439565b98975050505050505050565b604051601f8201601f191681016001600160401b038111828210171561486e5761486e61497b565b604052919050565b60006001600160401b0382111561488f5761488f61497b565b5060051b60200190565b600082198211156148ac576148ac614965565b500190565b6000826148cc57634e487b7160e01b81526012600452602481fd5b500490565b60008160001904831182151516156148eb576148eb614965565b500290565b60008282101561490257614902614965565b500390565b60005b8381101561492257818101518382015260200161490a565b838111156111cf5750506000910152565b60008161494257614942614965565b506000190190565b600060001982141561495e5761495e614965565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610d3057600080fd5b8015158114610d3057600080fdfe97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775a2646970667358221220f41119554752455d5198aa50d75e8bcd7ff6f6f41d557e7bd8aabd4c3702ba3b64736f6c63430008030033

Block Transaction Gas Used Reward
Age Block Fee Address BC Fee Address Voting Power Jailed Incoming
Block Uncle Number Difficulty Gas Used Reward
Loading
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.