ETH Price: $2,395.83 (+0.99%)

Contract

0xEe710f79aA85099e200be4d40Cdf1Bfb2B467a01
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Claim206791962024-09-04 19:05:5911 hrs ago1725476759IN
0xEe710f79...b2B467a01
0 ETH0.000417876.48548886
Stake206761462024-09-04 8:53:1121 hrs ago1725439991IN
0xEe710f79...b2B467a01
0 ETH0.000172522.00620406
Stake206711382024-09-03 16:06:5938 hrs ago1725379619IN
0xEe710f79...b2B467a01
0 ETH0.000445953.5539207
Claim206711342024-09-03 16:06:1138 hrs ago1725379571IN
0xEe710f79...b2B467a01
0 ETH0.000334573.67120466
Stake206665492024-09-03 0:44:352 days ago1725324275IN
0xEe710f79...b2B467a01
0 ETH0.000145061.59795904
Claim206665452024-09-03 0:43:472 days ago1725324227IN
0xEe710f79...b2B467a01
0 ETH0.000104161.61659862
Claim206626862024-09-02 11:48:112 days ago1725277691IN
0xEe710f79...b2B467a01
0 ETH0.000166342.58164913
Claim206606952024-09-02 5:08:353 days ago1725253715IN
0xEe710f79...b2B467a01
0 ETH0.000047760.58585542
Stake206558192024-09-01 12:48:233 days ago1725194903IN
0xEe710f79...b2B467a01
0 ETH0.000058210.64121268
Claim206558152024-09-01 12:47:353 days ago1725194855IN
0xEe710f79...b2B467a01
0 ETH0.000042070.65295838
Stake206536862024-09-01 5:39:234 days ago1725169163IN
0xEe710f79...b2B467a01
0 ETH0.000049910.54981158
Claim206536072024-09-01 5:23:354 days ago1725168215IN
0xEe710f79...b2B467a01
0 ETH0.000035660.55352912
Stake206532922024-09-01 4:20:354 days ago1725164435IN
0xEe710f79...b2B467a01
0 ETH0.000100921.11156593
Claim206532882024-09-01 4:19:474 days ago1725164387IN
0xEe710f79...b2B467a01
0 ETH0.000067461.04709197
Claim206433002024-08-30 18:51:115 days ago1725043871IN
0xEe710f79...b2B467a01
0 ETH0.00012471.36246398
Claim206432912024-08-30 18:49:235 days ago1725043763IN
0xEe710f79...b2B467a01
0 ETH0.000114271.24852136
Claim206432832024-08-30 18:47:475 days ago1725043667IN
0xEe710f79...b2B467a01
0 ETH0.000146191.59728473
Claim206432772024-08-30 18:46:355 days ago1725043595IN
0xEe710f79...b2B467a01
0 ETH0.000151761.65812056
Claim206432512024-08-30 18:41:235 days ago1725043283IN
0xEe710f79...b2B467a01
0 ETH0.000185882.03088342
Stake206368382024-08-29 21:11:236 days ago1724965883IN
0xEe710f79...b2B467a01
0 ETH0.000204971.69847746
Stake206339242024-08-29 11:24:596 days ago1724930699IN
0xEe710f79...b2B467a01
0 ETH0.000166321.83213608
Claim206339202024-08-29 11:24:116 days ago1724930651IN
0xEe710f79...b2B467a01
0 ETH0.000121991.89332035
Unstake206322702024-08-29 5:51:477 days ago1724910707IN
0xEe710f79...b2B467a01
0 ETH0.000136050.82341319
Claim206298792024-08-28 21:49:477 days ago1724881787IN
0xEe710f79...b2B467a01
0 ETH0.000081531
Stake206113722024-08-26 7:47:479 days ago1724658467IN
0xEe710f79...b2B467a01
0 ETH0.000109140.94204397
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To
189451082024-01-06 2:07:11243 days ago1704506831
0xEe710f79...b2B467a01
0.00059785 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Staking

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 500 runs

Other Settings:
default evmVersion
File 1 of 20 : Staking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import {IERC20, SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {TypeAndVersionInterface} from "./interfaces/TypeAndVersionInterface.sol";
import {Pausable} from "openzeppelin-contracts/contracts/security/Pausable.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
import {IStaking} from "./interfaces/IStaking.sol";
import {IStakingOwner} from "./interfaces/IStakingOwner.sol";
import {INodeStaking} from "./interfaces/INodeStaking.sol";
import {IMigratable} from "./interfaces/IMigratable.sol";
import {StakingPoolLib} from "./libraries/StakingPoolLib.sol";
import {RewardLib, SafeCast} from "./libraries/RewardLib.sol";
import {IMigrationTarget} from "./interfaces/IMigrationTarget.sol";

contract Staking is IStaking, IStakingOwner, INodeStaking, IMigratable, Ownable, TypeAndVersionInterface, Pausable {
    using StakingPoolLib for StakingPoolLib.Pool;
    using RewardLib for RewardLib.Reward;
    using SafeCast for uint256;
    using SafeERC20 for IERC20;

    /// @notice This struct defines the params required by the Staking contract's
    /// constructor.
    struct PoolConstructorParams {
        /// @notice The ARPA Token
        IERC20 arpa;
        /// @notice The initial maximum total stake amount across all stakers
        uint256 initialMaxPoolSize;
        /// @notice The initial maximum stake amount for a single community staker
        uint256 initialMaxCommunityStakeAmount;
        /// @notice The minimum stake amount that a community staker can stake
        uint256 minCommunityStakeAmount;
        /// @notice The stake amount that an operator should stake
        uint256 operatorStakeAmount;
        /// @notice The minimum number of node operators required to initialize the
        /// staking pool.
        uint256 minInitialOperatorCount;
        /// @notice The minimum reward duration after pool config updates and pool
        /// reward extensions
        uint256 minRewardDuration;
        /// @notice Used to calculate delegated stake amount
        /// = amount / delegation rate denominator = 100% / 100 = 1%
        uint256 delegationRateDenominator;
        /// @notice The freezing duration for stakers after unstaking
        uint256 unstakeFreezingDuration;
    }

    IERC20 internal immutable _arpa;
    StakingPoolLib.Pool internal _pool;
    RewardLib.Reward internal _reward;
    /// @notice The address of the controller contract
    address internal _controller;
    /// @notice The proposed address stakers will migrate funds to
    address internal _proposedMigrationTarget;
    /// @notice The timestamp of when the migration target was proposed at
    uint256 internal _proposedMigrationTargetAt;
    /// @notice The address stakers can migrate their funds to
    address internal _migrationTarget;

    /// @notice The stake amount that a node operator should stake
    uint256 internal immutable _operatorStakeAmount;
    /// @notice The minimum stake amount that a community staker can stake
    uint256 internal immutable _minCommunityStakeAmount;
    /// @notice The minimum number of node operators required to initialize the
    /// staking pool.
    uint256 internal immutable _minInitialOperatorCount;
    /// @notice The minimum reward duration after pool config updates and pool
    /// reward extensions
    uint256 internal immutable _minRewardDuration;
    /// @notice Used to calculate delegated stake amount
    /// = amount / delegation rate denominator = 100% / 100 = 1%
    uint256 internal immutable _delegationRateDenominator;
    /// @notice The freeze duration for stakers after unstaking
    uint256 internal immutable _unstakeFreezingDuration;

    event StakingConfigSet(
        address arpaAddress,
        uint256 initialMaxPoolSize,
        uint256 initialMaxCommunityStakeAmount,
        uint256 minCommunityStakeAmount,
        uint256 operatorStakeAmount,
        uint256 minInitialOperatorCount,
        uint256 minRewardDuration,
        uint256 delegationRateDenominator,
        uint256 unstakeFreezingDuration
    );

    constructor(PoolConstructorParams memory params) {
        if (address(params.arpa) == address(0)) revert InvalidZeroAddress();
        if (params.delegationRateDenominator == 0) revert InvalidDelegationRate();
        if (RewardLib.REWARD_PRECISION % params.delegationRateDenominator > 0) {
            revert InvalidDelegationRate();
        }
        if (params.operatorStakeAmount == 0) {
            revert InvalidOperatorStakeAmount();
        }
        if (params.minCommunityStakeAmount > params.initialMaxCommunityStakeAmount) {
            revert InvalidMinCommunityStakeAmount();
        }

        _pool._setConfig(params.initialMaxPoolSize, params.initialMaxCommunityStakeAmount);
        _arpa = params.arpa;
        _operatorStakeAmount = params.operatorStakeAmount;
        _minCommunityStakeAmount = params.minCommunityStakeAmount;
        _minInitialOperatorCount = params.minInitialOperatorCount;
        _minRewardDuration = params.minRewardDuration;
        _delegationRateDenominator = params.delegationRateDenominator;
        _unstakeFreezingDuration = params.unstakeFreezingDuration;

        emit StakingConfigSet(
            address(params.arpa),
            params.initialMaxPoolSize,
            params.initialMaxCommunityStakeAmount,
            params.minCommunityStakeAmount,
            params.operatorStakeAmount,
            params.minInitialOperatorCount,
            params.minRewardDuration,
            params.delegationRateDenominator,
            params.unstakeFreezingDuration
        );
    }

    // =======================
    // TypeAndVersionInterface
    // =======================

    /// @inheritdoc TypeAndVersionInterface
    function typeAndVersion() external pure override returns (string memory) {
        return "Staking 0.1.0";
    }

    // =============
    // IStakingOwner
    // =============

    /// @inheritdoc IStakingOwner
    function setController(address controller) external override(IStakingOwner) onlyOwner {
        if (controller == address(0)) revert InvalidZeroAddress();
        _controller = controller;

        emit ControllerSet(controller);
    }

    /// @inheritdoc IStakingOwner
    function setPoolConfig(uint256 maxPoolSize, uint256 maxCommunityStakeAmount)
        external
        override(IStakingOwner)
        onlyOwner
        whenActive
    {
        _pool._setConfig(maxPoolSize, maxCommunityStakeAmount);
    }

    /// @inheritdoc IStakingOwner
    function start(uint256 amount, uint256 rewardDuration) external override(IStakingOwner) onlyOwner {
        if (_reward.startTimestamp != 0) revert AlreadyInitialized();

        _pool._open(_minInitialOperatorCount);

        // We need to transfer ARPA balance before we initialize the reward to
        // calculate the new reward expiry timestamp.
        _arpa.safeTransferFrom(msg.sender, address(this), amount);

        _reward._initialize(_minRewardDuration, amount, rewardDuration);
    }

    /// @inheritdoc IStakingOwner
    function newReward(uint256 amount, uint256 rewardDuration)
        external
        override(IStakingOwner)
        onlyOwner
        whenInactive
    {
        _reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
        _reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());

        _arpa.safeTransferFrom(msg.sender, address(this), amount);

        _reward._initialize(_minRewardDuration, amount, rewardDuration);
    }

    /// @inheritdoc IStakingOwner
    function addReward(uint256 amount, uint256 rewardDuration) external override(IStakingOwner) onlyOwner whenActive {
        _reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
        _reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());

        _arpa.safeTransferFrom(msg.sender, address(this), amount);

        _reward._updateReward(amount, rewardDuration, _minRewardDuration);

        emit RewardLib.RewardAdded(amount, block.timestamp + rewardDuration);
    }

    /// @dev Required conditions for adding operators:
    /// - Operators can only be added to the pool if they have no prior stake.
    /// - Operators cannot be added to the pool after staking ends.
    /// @inheritdoc IStakingOwner
    function addOperators(address[] calldata operators) external override(IStakingOwner) onlyOwner {
        // If reward was initialized (meaning the pool was active) but the pool is
        // no longer active we want to prevent adding new operators.
        if (_reward.startTimestamp > 0 && !isActive()) {
            revert StakingPoolLib.InvalidPoolStatus(false, true);
        }

        _pool._addOperators(operators);
    }

    /// @inheritdoc IStakingOwner
    function emergencyPause() external override(IStakingOwner) onlyOwner {
        _pause();
    }

    /// @inheritdoc IStakingOwner
    function emergencyUnpause() external override(IStakingOwner) onlyOwner {
        _unpause();
    }

    // ===========
    // IMigratable
    // ===========

    /// @inheritdoc IMigratable
    function getMigrationTarget() external view override(IMigratable) returns (address) {
        return _migrationTarget;
    }

    /// @inheritdoc IMigratable
    function proposeMigrationTarget(address migrationTarget) external override(IMigratable) onlyOwner {
        if (
            migrationTarget.code.length == 0 || migrationTarget == address(this)
                || _proposedMigrationTarget == migrationTarget || _migrationTarget == migrationTarget
                || !IERC165(migrationTarget).supportsInterface(IMigrationTarget.migrateFrom.selector)
        ) {
            revert InvalidMigrationTarget();
        }

        _migrationTarget = address(0);
        _proposedMigrationTarget = migrationTarget;
        _proposedMigrationTargetAt = block.timestamp;
        emit MigrationTargetProposed(migrationTarget);
    }

    /// @inheritdoc IMigratable
    function acceptMigrationTarget() external override(IMigratable) onlyOwner {
        if (_proposedMigrationTarget == address(0)) {
            revert InvalidMigrationTarget();
        }

        if (block.timestamp < (uint256(_proposedMigrationTargetAt) + 7 days)) {
            revert AccessForbidden();
        }

        _migrationTarget = _proposedMigrationTarget;
        _proposedMigrationTarget = address(0);
        emit MigrationTargetAccepted(_migrationTarget);
    }

    /// @inheritdoc IMigratable
    function migrate(bytes calldata data) external override(IMigratable) whenInactive {
        if (_migrationTarget == address(0)) revert InvalidMigrationTarget();

        (uint256 amount, uint256 baseReward, uint256 delegationReward) = _exit(msg.sender);

        _arpa.safeTransfer(_migrationTarget, uint256(amount + baseReward + delegationReward));

        // call migrate function
        IMigrationTarget(_migrationTarget).migrateFrom(
            uint256(amount + baseReward + delegationReward), abi.encode(msg.sender, data)
        );

        emit Migrated(msg.sender, amount, baseReward, delegationReward, data);
    }

    // ========
    // INodeStaking
    // ========

    /// @inheritdoc INodeStaking
    function lock(address staker, uint256 amount) external override(INodeStaking) onlyController {
        StakingPoolLib.Staker storage stakerAccount = _pool.stakers[staker];
        if (!stakerAccount.isOperator) {
            revert StakingPoolLib.OperatorDoesNotExist(staker);
        }
        if (stakerAccount.stakedAmount < amount) {
            revert StakingPoolLib.InsufficientStakeAmount(amount);
        }
        stakerAccount.lockedStakeAmount += amount._toUint96();
        emit Locked(staker, amount);
    }

    /// @inheritdoc INodeStaking
    function unlock(address staker, uint256 amount) external override(INodeStaking) onlyController {
        StakingPoolLib.Staker storage stakerAccount = _pool.stakers[staker];
        if (!stakerAccount.isOperator) {
            revert StakingPoolLib.OperatorDoesNotExist(staker);
        }
        if (stakerAccount.lockedStakeAmount < amount) {
            revert INodeStaking.InadequateOperatorLockedStakingAmount(stakerAccount.lockedStakeAmount);
        }
        stakerAccount.lockedStakeAmount -= amount._toUint96();
        emit Unlocked(staker, amount);
    }

    /// @inheritdoc INodeStaking
    function slashDelegationReward(address staker, uint256 amount) external override(INodeStaking) onlyController {
        StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
        if (!stakerAccount.isOperator) {
            revert StakingPoolLib.OperatorDoesNotExist(staker);
        }
        uint256 earnedRewards = _reward._getOperatorEarnedDelegatedRewards(
            staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
        );
        // max capped by earnings
        uint256 slashedRewards = Math.min(amount, earnedRewards);
        _reward.missed[staker].delegated += slashedRewards._toUint96();

        _arpa.safeTransfer(owner(), slashedRewards);

        emit DelegationRewardSlashed(staker, slashedRewards);
    }

    /// @inheritdoc INodeStaking
    function getLockedAmount(address staker) external view override(INodeStaking) returns (uint256) {
        return _pool.stakers[staker].lockedStakeAmount;
    }

    // ========
    // IStaking
    // ========

    /// @inheritdoc IStaking
    function stake(uint256 amount) external override(IStaking) whenNotPaused {
        if (amount < RewardLib.REWARD_PRECISION) {
            revert StakingPoolLib.InsufficientStakeAmount(RewardLib.REWARD_PRECISION);
        }

        // Round down input amount to avoid cumulative rounding errors.
        uint256 remainder = amount % RewardLib.REWARD_PRECISION;
        if (remainder > 0) {
            amount -= remainder;
        }

        if (_pool._isOperator(msg.sender)) {
            _stakeAsOperator(msg.sender, amount);
        } else {
            _stakeAsCommunityStaker(msg.sender, amount);
        }

        _arpa.safeTransferFrom(msg.sender, address(this), amount);
    }

    /// @inheritdoc IStaking
    function unstake(uint256 amount) external override(IStaking) whenNotPaused {
        // Round down unstake amount to avoid cumulative rounding errors.
        uint256 remainder = amount % RewardLib.REWARD_PRECISION;
        if (remainder > 0) {
            amount -= remainder;
        }

        (uint256 baseReward, uint256 delegationReward) = _exit(msg.sender, amount, false);

        _arpa.safeTransfer(msg.sender, baseReward + delegationReward);

        emit Unstaked(msg.sender, amount, baseReward, delegationReward);
    }

    /// @inheritdoc IStaking
    function claim() external override(IStaking) whenNotPaused {
        claimReward();
        if (_pool.stakers[msg.sender].frozenPrincipals.length > 0) {
            claimFrozenPrincipal();
        }
    }

    /// @inheritdoc IStaking
    function claimReward() public override(IStaking) whenNotPaused {
        StakingPoolLib.Staker memory stakerAccount = _pool.stakers[msg.sender];
        if (stakerAccount.isOperator) {
            revert StakingPoolLib.NoBaseRewardForOperator();
        }

        uint256 accruedReward = _reward._calculateAccruedBaseRewards(
            RewardLib._getNonDelegatedAmount(stakerAccount.stakedAmount, _delegationRateDenominator),
            getTotalCommunityStakedAmount()
        );

        uint256 claimingReward = accruedReward - uint256(_reward.missed[msg.sender].base);

        _reward.missed[msg.sender].base = accruedReward._toUint96();

        _arpa.safeTransfer(msg.sender, claimingReward);

        emit RewardClaimed(msg.sender, claimingReward);
    }

    /// @inheritdoc IStaking
    function claimFrozenPrincipal() public override(IStaking) whenNotPaused {
        StakingPoolLib.FrozenPrincipal[] storage frozenPrincipals = _pool.stakers[msg.sender].frozenPrincipals;
        if (frozenPrincipals.length == 0) revert StakingPoolLib.FrozenPrincipalDoesNotExist(msg.sender);

        uint256 claimingPrincipal = 0;
        uint256 popCount = 0;
        for (uint256 i = 0; i < frozenPrincipals.length; i++) {
            StakingPoolLib.FrozenPrincipal storage frozenPrincipal = frozenPrincipals[i];
            if (frozenPrincipals[i].unlockTimestamp <= block.timestamp) {
                claimingPrincipal += frozenPrincipal.amount;
                _pool.totalFrozenAmount -= frozenPrincipal.amount;
                popCount++;
            } else {
                break;
            }
        }
        if (popCount > 0) {
            for (uint256 i = 0; i < frozenPrincipals.length - popCount; i++) {
                frozenPrincipals[i] = frozenPrincipals[i + popCount];
            }
            for (uint256 i = 0; i < popCount; i++) {
                frozenPrincipals.pop();
            }
        }

        if (claimingPrincipal > 0) {
            _arpa.safeTransfer(msg.sender, claimingPrincipal);
        }

        emit FrozenPrincipalClaimed(msg.sender, claimingPrincipal);
    }

    /// @inheritdoc IStaking
    function getStake(address staker) public view override(IStaking) returns (uint256) {
        return _pool.stakers[staker].stakedAmount;
    }

    /// @inheritdoc IStaking
    function isOperator(address staker) external view override(IStaking) returns (bool) {
        return _pool._isOperator(staker);
    }

    /// @inheritdoc IStaking
    function isActive() public view override(IStaking) returns (bool) {
        return _pool.state.isOpen && !_reward._isDepleted();
    }

    /// @inheritdoc IStaking
    function getMaxPoolSize() external view override(IStaking) returns (uint256) {
        return uint256(_pool.limits.maxPoolSize);
    }

    /// @inheritdoc IStaking
    function getCommunityStakerLimits() external view override(IStaking) returns (uint256, uint256) {
        return (_minCommunityStakeAmount, uint256(_pool.limits.maxCommunityStakeAmount));
    }

    /// @inheritdoc IStaking
    function getOperatorLimit() external view override(IStaking) returns (uint256) {
        return _operatorStakeAmount;
    }

    /// @inheritdoc IStaking
    function getRewardTimestamps() external view override(IStaking) returns (uint256, uint256) {
        return (uint256(_reward.startTimestamp), uint256(_reward.endTimestamp));
    }

    /// @inheritdoc IStaking
    function getRewardRate() external view override(IStaking) returns (uint256) {
        return uint256(_reward.rate);
    }

    /// @inheritdoc IStaking
    function getDelegationRateDenominator() external view override(IStaking) returns (uint256) {
        return _delegationRateDenominator;
    }

    /// @inheritdoc IStaking
    function getAvailableReward() public view override(IStaking) returns (uint256) {
        return _arpa.balanceOf(address(this)) - getTotalStakedAmount() - _pool.totalFrozenAmount;
    }

    /// @inheritdoc IStaking
    function getBaseReward(address staker) public view override(IStaking) returns (uint256) {
        uint256 stakedAmount = _pool.stakers[staker].stakedAmount;
        if (stakedAmount == 0) return 0;

        if (_pool._isOperator(staker)) {
            return 0;
        }

        return _reward._calculateAccruedBaseRewards(
            RewardLib._getNonDelegatedAmount(stakedAmount, _delegationRateDenominator), getTotalCommunityStakedAmount()
        ) - uint256(_reward.missed[staker].base);
    }

    /// @inheritdoc IStaking
    function getDelegationReward(address staker) public view override(IStaking) returns (uint256) {
        StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
        if (!stakerAccount.isOperator) return 0;
        if (stakerAccount.stakedAmount == 0) return 0;
        return _reward._getOperatorEarnedDelegatedRewards(
            staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
        );
    }

    /// @inheritdoc IStaking
    function getTotalDelegatedAmount() public view override(IStaking) returns (uint256) {
        return RewardLib._getDelegatedAmount(_pool.state.totalCommunityStakedAmount, _delegationRateDenominator);
    }

    /// @inheritdoc IStaking
    function getDelegatesCount() external view override(IStaking) returns (uint256) {
        return uint256(_reward.delegated.delegatesCount);
    }

    function getCommunityStakersCount() external view returns (uint256) {
        return uint256(_reward.base.communityStakersCount);
    }

    /// @inheritdoc IStaking
    function getTotalStakedAmount() public view override(IStaking) returns (uint256) {
        return _pool._getTotalStakedAmount();
    }

    /// @inheritdoc IStaking
    function getTotalCommunityStakedAmount() public view override(IStaking) returns (uint256) {
        return _pool.state.totalCommunityStakedAmount;
    }

    /// @inheritdoc IStaking
    function getTotalFrozenAmount() external view override(IStaking) returns (uint256) {
        return _pool.totalFrozenAmount;
    }

    /// @inheritdoc IStaking
    function getFrozenPrincipal(address staker)
        external
        view
        override(IStaking)
        returns (uint96[] memory amounts, uint256[] memory unlockTimestamps)
    {
        StakingPoolLib.FrozenPrincipal[] memory frozenPrincipals = _pool.stakers[staker].frozenPrincipals;
        amounts = new uint96[](frozenPrincipals.length);
        unlockTimestamps = new uint256[](frozenPrincipals.length);
        for (uint256 i = 0; i < frozenPrincipals.length; i++) {
            amounts[i] = frozenPrincipals[i].amount;
            unlockTimestamps[i] = frozenPrincipals[i].unlockTimestamp;
        }
    }

    /// @inheritdoc IStaking
    function getClaimablePrincipalAmount(address) external view returns (uint256 claimingPrincipal) {
        StakingPoolLib.FrozenPrincipal[] storage frozenPrincipals = _pool.stakers[msg.sender].frozenPrincipals;
        if (frozenPrincipals.length == 0) return 0;

        for (uint256 i = 0; i < frozenPrincipals.length; i++) {
            StakingPoolLib.FrozenPrincipal storage frozenPrincipal = frozenPrincipals[i];
            if (frozenPrincipals[i].unlockTimestamp <= block.timestamp) {
                claimingPrincipal += frozenPrincipal.amount;
            } else {
                break;
            }
        }
    }

    /// @inheritdoc IStaking
    function getArpaToken() public view override(IStaking) returns (address) {
        return address(_arpa);
    }

    /// @inheritdoc IStaking
    function getController() external view override(IStaking) returns (address) {
        return _controller;
    }

    // =======
    // Internal
    // =======

    /// @notice Helper function for when a community staker enters the pool
    /// @param staker The staker address
    /// @param amount The amount of principal staked
    function _stakeAsCommunityStaker(address staker, uint256 amount) internal whenActive {
        uint256 currentStakedAmount = _pool.stakers[staker].stakedAmount;
        uint256 newStakedAmount = currentStakedAmount + amount;
        // Check that the amount is greater than or equal to the minimum required
        if (newStakedAmount < _minCommunityStakeAmount) {
            revert StakingPoolLib.InsufficientStakeAmount(_minCommunityStakeAmount);
        }

        // Check that the amount is less than or equal to the maximum allowed
        uint256 maxCommunityStakeAmount = uint256(_pool.limits.maxCommunityStakeAmount);
        if (newStakedAmount > maxCommunityStakeAmount) {
            revert StakingPoolLib.ExcessiveStakeAmount(maxCommunityStakeAmount - currentStakedAmount);
        }

        // Check if the amount supplied increases the total staked amount above
        // the maximum pool size
        uint256 remainingPoolSpace = _pool._getRemainingPoolSpace();
        if (amount > remainingPoolSpace) {
            revert StakingPoolLib.ExcessiveStakeAmount(remainingPoolSpace);
        }

        _reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
        _reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());

        // On first stake
        if (currentStakedAmount == 0) {
            _reward.base.communityStakersCount += 1;
        }

        uint256 extraNonDelegatedAmount = RewardLib._getNonDelegatedAmount(amount, _delegationRateDenominator);
        _reward.missed[staker].base +=
            _reward._calculateAccruedBaseRewards(extraNonDelegatedAmount, getTotalCommunityStakedAmount())._toUint96();
        _pool.state.totalCommunityStakedAmount += amount._toUint96();
        _pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
        emit Staked(staker, amount, newStakedAmount);
    }

    /// @notice Helper function for when an operator enters the pool
    /// @param staker The staker address
    /// @param amount The amount of principal staked
    function _stakeAsOperator(address staker, uint256 amount) internal {
        StakingPoolLib.Staker storage operator = _pool.stakers[staker];
        uint256 currentStakedAmount = operator.stakedAmount;
        uint256 newStakedAmount = currentStakedAmount + amount;

        // Check that the amount is greater than or less than the required
        if (newStakedAmount < _operatorStakeAmount) {
            revert StakingPoolLib.InsufficientStakeAmount(_operatorStakeAmount);
        }
        if (newStakedAmount > _operatorStakeAmount) {
            revert StakingPoolLib.ExcessiveStakeAmount(newStakedAmount - _operatorStakeAmount);
        }

        // On first stake
        if (currentStakedAmount == 0) {
            _reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
            uint8 delegatesCount = _reward.delegated.delegatesCount;

            // Prior to the first operator staking, we reset the accumulated value
            // so it doesn't count towards missed rewards.
            if (delegatesCount == 0) {
                delete _reward.delegated.cumulativePerDelegate;
            }

            _reward.delegated.delegatesCount = delegatesCount + 1;

            _reward.missed[staker].delegated = _reward.delegated.cumulativePerDelegate;
        }

        _pool.state.totalOperatorStakedAmount += amount._toUint96();
        _pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
        emit Staked(staker, amount, newStakedAmount);
    }

    /// @notice Helper function when staker exits the pool
    /// @param staker The staker address
    function _exit(address staker) internal returns (uint256, uint256, uint256) {
        StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
        if (stakerAccount.stakedAmount == 0) {
            revert StakingPoolLib.StakeNotFound(staker);
        }
        if (stakerAccount.lockedStakeAmount > 0) {
            revert StakingPoolLib.ExistingLockedStakeFound(staker);
        }
        (uint256 baseReward, uint256 delegationReward) = _exit(staker, stakerAccount.stakedAmount, true);
        return (stakerAccount.stakedAmount, baseReward, delegationReward);
    }

    /// @notice Helper function when staker exits the pool
    /// @param staker The staker address
    function _exit(address staker, uint256 amount, bool isMigrate) internal returns (uint256, uint256) {
        StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
        if (amount == 0) {
            revert StakingPoolLib.UnstakeWithZeroAmount(staker);
        }
        if (stakerAccount.stakedAmount < amount) {
            revert StakingPoolLib.InadequateStakingAmount(stakerAccount.stakedAmount);
        }

        _reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
        _reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());

        if (stakerAccount.isOperator) {
            if (amount != _operatorStakeAmount) {
                revert StakingPoolLib.UnstakeOperatorWithPartialAmount(staker);
            }

            if (stakerAccount.lockedStakeAmount > 0) {
                revert StakingPoolLib.ExistingLockedStakeFound(staker);
            }

            uint256 delegationReward = _reward._getOperatorEarnedDelegatedRewards(
                staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
            );

            _pool.state.totalOperatorStakedAmount -= amount._toUint96();
            _pool.stakers[staker].stakedAmount -= amount._toUint96();

            if (!isMigrate) {
                _pool.totalFrozenAmount += amount._toUint96();
                _pool.stakers[staker].frozenPrincipals.push(
                    StakingPoolLib.FrozenPrincipal(amount._toUint96(), block.timestamp + _unstakeFreezingDuration)
                );
            }
            _reward.delegated.delegatesCount -= 1;

            _reward.missed[staker].delegated = _reward.delegated.cumulativePerDelegate;

            return (0, delegationReward);
        } else {
            uint256 baseReward = _reward._calculateAccruedBaseRewards(
                RewardLib._getNonDelegatedAmount(stakerAccount.stakedAmount, _delegationRateDenominator),
                getTotalCommunityStakedAmount()
            ) - uint256(_reward.missed[staker].base);

            _pool.state.totalCommunityStakedAmount -= amount._toUint96();
            _pool.stakers[staker].stakedAmount -= amount._toUint96();

            if (_pool.stakers[staker].stakedAmount == 0) {
                _reward.base.communityStakersCount -= 1;
            }

            if (!isMigrate) {
                _pool.totalFrozenAmount += amount._toUint96();
                _pool.stakers[staker].frozenPrincipals.push(
                    StakingPoolLib.FrozenPrincipal(amount._toUint96(), block.timestamp + _unstakeFreezingDuration)
                );
            }

            _reward.missed[staker].base = _reward._calculateAccruedBaseRewards(
                RewardLib._getNonDelegatedAmount(_pool.stakers[staker].stakedAmount, _delegationRateDenominator),
                getTotalCommunityStakedAmount()
            )._toUint96();

            return (baseReward, 0);
        }
    }

    // =========
    // Modifiers
    // =========

    /// @dev Having a private function for the modifer saves on the contract size
    function _isActive() private view {
        if (!isActive()) revert StakingPoolLib.InvalidPoolStatus(false, true);
    }

    /// @dev Reverts if the staking pool is inactive (not open for staking or
    /// expired)
    modifier whenActive() {
        _isActive();

        _;
    }

    /// @dev Reverts if the staking pool is active (open for staking)
    modifier whenInactive() {
        if (isActive()) revert StakingPoolLib.InvalidPoolStatus(true, false);

        _;
    }

    /// @dev Reverts if not sent from the LINK token
    modifier onlyController() {
        if (msg.sender != _controller) revert SenderNotController();

        _;
    }
}

File 2 of 20 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.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));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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 3 of 20 : TypeAndVersionInterface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

abstract contract TypeAndVersionInterface {
    function typeAndVersion() external pure virtual returns (string memory);
}

File 4 of 20 : Pausable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

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

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

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

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

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

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

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

File 5 of 20 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * 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 Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

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

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

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        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);
    }
}

File 6 of 20 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol)

pragma solidity ^0.8.0;

import "../utils/introspection/IERC165.sol";

File 7 of 20 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

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

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

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

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

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

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

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

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

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

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

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 8 of 20 : IStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IStaking {
    /// @notice This event is emitted when the controller is set.
    /// @param controller Controller address
    event ControllerSet(address controller);

    /// @notice This event is emitted when a staker adds stake to the pool.
    /// @param staker Staker address
    /// @param newStake New principal amount staked
    /// @param totalStake Total principal amount staked
    event Staked(address staker, uint256 newStake, uint256 totalStake);
    /// @notice This event is emitted when a staker exits the pool.
    /// @param staker Staker address
    /// @param principal Principal amount frozen after unstaking
    /// @param baseReward base reward earned
    /// @param delegationReward delegation reward earned, if any
    event Unstaked(address staker, uint256 principal, uint256 baseReward, uint256 delegationReward);

    /// @notice This event is emitted when a staker claims base reward.
    /// @param staker Staker address
    /// @param baseReward Base reward amount claimed
    event RewardClaimed(address staker, uint256 baseReward);

    /// @notice This event is emitted when a staker claims frozen principal.
    /// @param staker Staker address
    /// @param principal Principal amount claimed
    event FrozenPrincipalClaimed(address staker, uint256 principal);

    /// @notice This error is thrown whenever an address does not have access
    /// to successfully execute a transaction
    error AccessForbidden();

    /// @notice This error is thrown whenever a zero-address is supplied when
    /// a non-zero address is required
    error InvalidZeroAddress();

    /// @notice This error is thrown whenever the sender is not controller contract
    error SenderNotController();

    /// @notice This function allows stakers to stake.
    function stake(uint256 amount) external;

    /// @notice This function allows stakers to unstake.
    /// It returns base and delegation rewards, and makes principle frozen for later claiming.
    function unstake(uint256 amount) external;

    /// @notice This function allows community stakers to claim base rewards and frozen principals(if any).
    function claim() external;

    /// @notice This function allows stakers to claim base rewards.
    function claimReward() external;

    /// @notice This function allows stakers to claim frozen principals.
    function claimFrozenPrincipal() external;

    /// @return address ARPA token contract's address that is used by the pool
    function getArpaToken() external view returns (address);

    /// @param staker address
    /// @return uint256 staker's staked principal amount
    function getStake(address staker) external view returns (uint256);

    /// @notice Returns true if an address is an operator
    function isOperator(address staker) external view returns (bool);

    /// @notice The staking pool starts closed and only allows
    /// stakers to stake once it's opened
    /// @return bool pool status
    function isActive() external view returns (bool);

    /// @return uint256 current maximum staking pool size
    function getMaxPoolSize() external view returns (uint256);

    /// @return uint256 minimum amount that can be staked by a community staker
    /// @return uint256 maximum amount that can be staked by a community staker
    function getCommunityStakerLimits() external view returns (uint256, uint256);

    /// @return uint256 amount that should be staked by an operator
    function getOperatorLimit() external view returns (uint256);

    /// @return uint256 reward initialization timestamp
    /// @return uint256 reward expiry timestamp
    function getRewardTimestamps() external view returns (uint256, uint256);

    /// @return uint256 current reward rate, expressed in arpa weis per second
    function getRewardRate() external view returns (uint256);

    /// @return uint256 current delegation rate
    function getDelegationRateDenominator() external view returns (uint256);

    /// @return uint256 total amount of ARPA tokens made available for rewards in
    /// ARPA wei
    /// @dev This reflects how many rewards were made available over the
    /// lifetime of the staking pool.
    function getAvailableReward() external view returns (uint256);

    /// @return uint256 amount of base rewards earned by a staker in ARPA wei
    function getBaseReward(address) external view returns (uint256);

    /// @return uint256 amount of delegation rewards earned by an operator in ARPA wei
    function getDelegationReward(address) external view returns (uint256);

    /// @notice Total delegated amount is calculated by dividing the total
    /// community staker staked amount by the delegation rate, i.e.
    /// totalDelegatedAmount = pool.totalCommunityStakedAmount / delegationRateDenominator
    /// @return uint256 staked amount that is used when calculating delegation rewards in ARPA wei
    function getTotalDelegatedAmount() external view returns (uint256);

    /// @notice Delegates count increases after an operator is added to the list
    /// of operators and stakes the required amount.
    /// @return uint256 number of staking operators that are eligible for delegation rewards
    function getDelegatesCount() external view returns (uint256);

    /// @notice This count all community stakers that have a staking balance greater than 0.
    /// @return uint256 number of staking community stakers that are eligible for base rewards
    function getCommunityStakersCount() external view returns (uint256);

    /// @return uint256 total amount staked by community stakers and operators in ARPA wei
    function getTotalStakedAmount() external view returns (uint256);

    /// @return uint256 total amount staked by community stakers in ARPA wei
    function getTotalCommunityStakedAmount() external view returns (uint256);

    /// @return uint256 the sum of frozen operator principals that have not been
    /// withdrawn from the staking pool in ARPA wei.
    /// @dev Used to make sure that contract's balance is correct.
    /// total staked amount + total frozen amount + available rewards = current balance
    function getTotalFrozenAmount() external view returns (uint256);

    /// @return amounts total amounts of ARPA wei that is currently frozen by the staker
    /// @return unlockTimestamps timestamps when the frozen principal can be withdrawn
    function getFrozenPrincipal(address)
        external
        view
        returns (uint96[] memory amounts, uint256[] memory unlockTimestamps);

    /// @return uint256 amount of ARPA wei that can be claimed as frozen principal by a staker
    function getClaimablePrincipalAmount(address) external view returns (uint256);

    /// @return address controller contract's address that is used by the pool
    function getController() external view returns (address);
}

File 9 of 20 : IStakingOwner.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

/// @notice Owner functions restricted to the setup and maintenance
/// of the staking contract by the owner.
interface IStakingOwner {
    /// @notice This error is thrown when an zero delegation rate is supplied
    error InvalidDelegationRate();

    /// @notice This error is thrown when an invalid operator stake amount is
    /// supplied
    error InvalidOperatorStakeAmount();

    /// @notice This error is thrown when an invalid min community stake amount
    /// is supplied
    error InvalidMinCommunityStakeAmount();

    /// @notice This error is thrown when the reward is already initialized
    error AlreadyInitialized();

    /// @notice Adds one or more operators to a list of operators
    /// @dev Should only callable by the Owner
    /// @param operators A list of operator addresses to add
    function addOperators(address[] calldata operators) external;

    /// @notice This function can be called to add rewards to the pool when the reward is depleted
    /// @dev Should only callable by the Owner
    /// @param amount The amount of rewards to add to the pool
    /// @param rewardDuration The duration of the reward
    function newReward(uint256 amount, uint256 rewardDuration) external;

    /// @notice This function can be called to add rewards to the pool when the reward is not depleted
    /// @dev Should only be callable by the owner
    /// @param amount The amount of rewards to add to the pool
    /// @param rewardDuration The duration of the reward
    function addReward(uint256 amount, uint256 rewardDuration) external;

    /// @notice Set the pool config
    /// @param maxPoolSize The max amount of staked ARPA by community stakers allowed in the pool
    /// @param maxCommunityStakeAmount The max amount of ARPA a community staker can stake
    function setPoolConfig(uint256 maxPoolSize, uint256 maxCommunityStakeAmount) external;

    /// @notice Set controller contract address
    /// @dev Should only be callable by the owner
    /// @param controller The address of the controller contract
    function setController(address controller) external;

    /// @notice Transfers ARPA tokens and initializes the reward
    /// @dev Uses ERC20 approve + transferFrom flow
    /// @param amount rewards amount in ARPA
    /// @param rewardDuration rewards duration in seconds
    function start(uint256 amount, uint256 rewardDuration) external;

    /// @notice This function pauses staking
    /// @dev Sets the pause flag to true
    function emergencyPause() external;

    /// @notice This function unpauses staking
    /// @dev Sets the pause flag to false
    function emergencyUnpause() external;
}

File 10 of 20 : INodeStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface INodeStaking {
    /// @notice This event is emitted when a node locks stake in the pool.
    /// @param staker Staker address
    /// @param newLock New principal amount locked
    event Locked(address staker, uint256 newLock);

    /// @notice This event is emitted when a node unlocks stake in the pool.
    /// @param staker Staker address
    /// @param newUnlock New principal amount unlocked
    event Unlocked(address staker, uint256 newUnlock);

    /// @notice This event is emitted when a node gets delegation reward slashed.
    /// @param staker Staker address
    /// @param amount Amount slashed
    event DelegationRewardSlashed(address staker, uint256 amount);

    /// @notice This error is raised when attempting to unlock with more than the current locked staking amount
    /// @param currentLockedStakingAmount Current locked staking amount
    error InadequateOperatorLockedStakingAmount(uint256 currentLockedStakingAmount);

    /// @notice This function allows controller to lock staking amount for a node.
    /// @param staker Node address
    /// @param amount Amount to lock
    function lock(address staker, uint256 amount) external;

    /// @notice This function allows controller to unlock staking amount for a node.
    /// @param staker Node address
    /// @param amount Amount to unlock
    function unlock(address staker, uint256 amount) external;

    /// @notice This function allows controller to slash delegation reward of a node.
    /// @param staker Node address
    /// @param amount Amount to slash
    function slashDelegationReward(address staker, uint256 amount) external;

    /// @notice This function returns the locked amount of a node.
    /// @param staker Node address
    function getLockedAmount(address staker) external view returns (uint256);
}

File 11 of 20 : IMigratable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IMigratable {
    /// @notice This event is emitted when a migration target is proposed by the contract owner.
    /// @param migrationTarget Contract address to migrate stakes to.
    event MigrationTargetProposed(address migrationTarget);
    /// @notice This event is emitted after a 7 day period has passed since a migration target is proposed, and the target is accepted.
    /// @param migrationTarget Contract address to migrate stakes to.
    event MigrationTargetAccepted(address migrationTarget);
    /// @notice This event is emitted when a staker migrates their stake to the migration target.
    /// @param staker Staker address
    /// @param principal Principal amount deposited
    /// @param baseReward Amount of base rewards withdrawn
    /// @param delegationReward Amount of delegation rewards withdrawn (if applicable)
    /// @param data Migration payload
    event Migrated(address staker, uint256 principal, uint256 baseReward, uint256 delegationReward, bytes data);

    /// @notice This error is raised when the contract owner supplies a non-contract migration target.
    error InvalidMigrationTarget();

    /// @notice This function returns the migration target contract address
    function getMigrationTarget() external view returns (address);

    /// @notice This function allows the contract owner to set a proposed
    /// migration target address. If the migration target is valid it renounces
    /// the previously accepted migration target (if any).
    /// @param migrationTarget Contract address to migrate stakes to.
    function proposeMigrationTarget(address migrationTarget) external;

    /// @notice This function allows the contract owner to accept a proposed migration target address after a waiting period.
    function acceptMigrationTarget() external;

    /// @notice This function allows stakers to migrate funds to a new staking pool.
    /// @param data Migration path details
    function migrate(bytes calldata data) external;
}

File 12 of 20 : StakingPoolLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

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

library StakingPoolLib {
    using SafeCast for uint256;

    /// @notice This event is emitted when the staking pool is opened for stakers
    event PoolOpened();
    /// @notice This event is emitted when the staking pool's maximum size is
    /// increased
    /// @param maxPoolSize the new maximum pool size
    event PoolSizeIncreased(uint256 maxPoolSize);
    /// @notice This event is emitted when the maximum stake amount
    // for community stakers is increased
    /// @param maxStakeAmount the new maximum stake amount
    event MaxCommunityStakeAmountIncreased(uint256 maxStakeAmount);
    /// @notice This event is emitted when an operator is added
    /// @param operator address of the operator that was added to the staking pool
    event OperatorAdded(address operator);

    /// @notice Surfaces the required pool status to perform an operation
    /// (true if open / false if closed)
    /// @param currentStatus current status of the pool
    /// @param requiredStatus required status of the pool to proceed
    error InvalidPoolStatus(bool currentStatus, bool requiredStatus);
    /// @notice This error is raised when attempting to decrease maximum pool size.
    /// @param maxPoolSize the current maximum pool size
    error InvalidPoolSize(uint256 maxPoolSize);
    /// @notice This error is raised when attempting to decrease maximum stake amount
    /// for community stakers or node operators
    /// @param maxStakeAmount the current maximum stake amount
    error InvalidMaxStakeAmount(uint256 maxStakeAmount);
    /// @param requiredAmount minimum required stake amount
    error InsufficientStakeAmount(uint256 requiredAmount);
    /// @notice This error is raised when stakers attempt to stake past pool limits.
    /// @param remainingAmount maximum remaining amount that can be staked. This is
    /// the difference between the existing staked amount and the individual and global limits.
    error ExcessiveStakeAmount(uint256 remainingAmount);
    /// @notice This error is raised when stakers attempt to exit the pool.
    /// @param staker address of the staker who attempted to withdraw funds
    error StakeNotFound(address staker);
    /// @notice This error is raised when addresses with existing stake is added as an operator.
    /// @param staker address of the staker who is being added as an operator
    error ExistingStakeFound(address staker);
    /// @notice This error is raised when an address is duplicated in the supplied list of operators.
    /// This can happen in addOperators and setFeedOperators functions.
    /// @param operator address of the operator
    error OperatorAlreadyExists(address operator);
    /// @notice This error is raised when lock/unlock/slash is called on an operator that does not exist.
    /// @param operator address of the operator
    error OperatorDoesNotExist(address operator);
    /// @notice This error is raised when attempting to claim rewards by an operator.
    error NoBaseRewardForOperator();
    /// @notice This error is raised when attempting to start staking with less
    /// than the minimum required node operators
    /// @param currentOperatorsCount The current number of operators in the staking pool
    /// @param minInitialOperatorsCount The minimum required number of operators
    /// in the staking pool before opening
    error InadequateInitialOperatorsCount(uint256 currentOperatorsCount, uint256 minInitialOperatorsCount);
    /// @notice This error is raised when attempting to unstake with more than the current staking amount.
    error InadequateStakingAmount(uint256 currentStakingAmount);
    /// @notice This error is raised when attempting to claim frozen principal that does not exist.
    error FrozenPrincipalDoesNotExist(address staker);
    /// @notice This error is raised when attempting to unstake with zero amount.
    error UnstakeWithZeroAmount(address staker);
    /// @notice This error is raised when attempting to unstake with partial amount by an operator.
    error UnstakeOperatorWithPartialAmount(address operator);
    /// @notice This error is raised when attempting to unstake with existing locked staking amount.
    error ExistingLockedStakeFound(address operator);

    struct PoolLimits {
        // The max amount of staked ARPA by community stakers allowed in the pool
        uint96 maxPoolSize;
        // The max amount of ARPA a community staker can stake
        uint96 maxCommunityStakeAmount;
    }

    struct PoolState {
        // Flag that signals if the staking pool is open for staking
        bool isOpen;
        // Total number of operators added to the staking pool
        uint8 operatorsCount;
        // Total amount of ARPA staked by community stakers
        uint96 totalCommunityStakedAmount;
        // Total amount of ARPA staked by operators
        uint96 totalOperatorStakedAmount;
    }

    struct FrozenPrincipal {
        // Amount of ARPA frozen after unstaking
        uint96 amount;
        // Timestamp when the principal is unlocked
        uint256 unlockTimestamp;
    }

    struct Staker {
        // Flag that signals whether a staker is an operator
        bool isOperator;
        // Amount of ARPA staked by a staker
        uint96 stakedAmount;
        // Frozen principals of a staker
        FrozenPrincipal[] frozenPrincipals;
        // Locked staking amount of an operator
        uint96 lockedStakeAmount;
    }

    struct Pool {
        mapping(address => Staker) stakers;
        PoolState state;
        PoolLimits limits;
        // Sum of frozen principals that have not been withdrawn.
        // Used to make sure that contract's balance is correct.
        // total staked amount + total frozen amount + available rewards = current balance
        uint256 totalFrozenAmount;
    }

    /// @notice Sets staking pool parameters
    /// @param maxPoolSize Maximum total stake amount across all stakers
    /// @param maxCommunityStakeAmount Maximum stake amount for a single community staker
    function _setConfig(Pool storage pool, uint256 maxPoolSize, uint256 maxCommunityStakeAmount) internal {
        if (pool.limits.maxPoolSize > maxPoolSize) {
            revert InvalidPoolSize(maxPoolSize);
        }
        if (pool.limits.maxCommunityStakeAmount > maxCommunityStakeAmount) {
            revert InvalidMaxStakeAmount(maxCommunityStakeAmount);
        }

        if (pool.limits.maxPoolSize != maxPoolSize) {
            pool.limits.maxPoolSize = maxPoolSize._toUint96();
            emit PoolSizeIncreased(maxPoolSize);
        }
        if (pool.limits.maxCommunityStakeAmount != maxCommunityStakeAmount) {
            pool.limits.maxCommunityStakeAmount = maxCommunityStakeAmount._toUint96();
            emit MaxCommunityStakeAmountIncreased(maxCommunityStakeAmount);
        }
    }

    /// @notice Opens the staking pool
    function _open(Pool storage pool, uint256 minInitialOperatorCount) internal {
        if (uint256(pool.state.operatorsCount) < minInitialOperatorCount) {
            revert InadequateInitialOperatorsCount(pool.state.operatorsCount, minInitialOperatorCount);
        }
        pool.state.isOpen = true;
        emit PoolOpened();
    }

    /// @notice Returns true if a supplied staker address is in the operators list
    /// @param staker Address of a staker
    /// @return bool
    function _isOperator(Pool storage pool, address staker) internal view returns (bool) {
        return pool.stakers[staker].isOperator;
    }

    /// @notice Returns the sum of all principal staked in the pool
    /// @return totalStakedAmount
    function _getTotalStakedAmount(Pool storage pool) internal view returns (uint256) {
        StakingPoolLib.PoolState memory poolState = pool.state;
        return uint256(poolState.totalCommunityStakedAmount) + uint256(poolState.totalOperatorStakedAmount);
    }

    /// @notice Returns the amount of remaining space available in the pool for
    /// community stakers. Community stakers can only stake up to this amount
    /// even if they are within their individual limits.
    /// @return remainingPoolSpace
    function _getRemainingPoolSpace(Pool storage pool) internal view returns (uint256) {
        StakingPoolLib.PoolState memory poolState = pool.state;
        return uint256(pool.limits.maxPoolSize) - uint256(poolState.totalCommunityStakedAmount);
    }

    /// @dev Required conditions for adding operators:
    /// - Operators can only been added to the pool if they have no prior stake.
    /// - Operators cannot be added to the pool after staking ends.
    function _addOperators(Pool storage pool, address[] calldata operators) internal {
        for (uint256 i; i < operators.length; i++) {
            if (pool.stakers[operators[i]].isOperator) {
                revert OperatorAlreadyExists(operators[i]);
            }
            if (pool.stakers[operators[i]].stakedAmount > 0) {
                revert ExistingStakeFound(operators[i]);
            }
            pool.stakers[operators[i]].isOperator = true;
            emit OperatorAdded(operators[i]);
        }

        // Safely update operators count with respect to the maximum of 255 operators
        pool.state.operatorsCount = pool.state.operatorsCount + operators.length._toUint8();
    }
}

File 13 of 20 : RewardLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import {SafeCast} from "./SafeCast.sol";
import {StakingPoolLib} from "./StakingPoolLib.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";

library RewardLib {
    using SafeCast for uint256;

    /// @notice emitted when the reward is initialized for the first time
    /// @param available the amount of rewards available for distribution in the
    /// staking pool
    /// @param startTimestamp the start timestamp when rewards are started
    /// @param endTimestamp the timestamp when the reward will run out
    event RewardInitialized(uint256 available, uint256 startTimestamp, uint256 endTimestamp);
    /// @notice emitted when owner adds more rewards to the pool
    /// @param amountAdded the amount of ARPA rewards added to the pool
    /// @param endTimestamp the timestamp when the reward will run out
    event RewardAdded(uint256 amountAdded, uint256 endTimestamp);
    /// @notice emitted when owner withdraws leftover rewards
    /// @param amount the amount of rewards withdrawn
    event RewardWithdrawn(uint256 amount);
    /// @notice emitted when an  operator gets slashed.
    /// Node operators are not slashed more than the amount of rewards they
    /// have earned.
    event RewardSlashed(address[] operator, uint256[] slashedDelegatedRewards);

    /// @notice This error is thrown when the updated reward duration is too short
    error RewardDurationTooShort();

    /// @notice This is the reward calculation precision variable. ARPA token has the
    /// 1e18 multiplier which means that rewards are floored after 6 decimals
    /// points. Micro ARPA is the smallest unit that is eligible for rewards.
    uint256 internal constant REWARD_PRECISION = 1e12;

    struct DelegatedRewards {
        // Count of delegates who are eligible for a share of a reward
        uint8 delegatesCount;
        // Tracks base reward amounts that goes to an operator as delegation rewards.
        // Used to correctly account for any changes in operator count, delegated amount, or reward rate.
        uint96 cumulativePerDelegate;
        // Timestamp of the last time accumulate was called
        // `startTimestamp` <= `delegated.lastAccumulateTimestamp`
        uint32 lastAccumulateTimestamp;
    }

    struct BaseRewards {
        // Count of community stakers who are eligible for a share of a reward
        uint32 communityStakersCount;
        // The cumulative ARPA accrued per stake from past reward rates
        // expressed in ARPA wei per micro ARPA
        uint96 cumulativePerShare;
        // Timestamp of the last time the base reward rate was accumulated
        uint32 lastAccumulateTimestamp;
    }

    struct MissedRewards {
        // Tracks missed base rewards that are deducted from late stakers
        uint96 base;
        // Tracks missed delegation rewards that are deducted from late delegates
        uint96 delegated;
    }

    struct Reward {
        mapping(address => MissedRewards) missed;
        DelegatedRewards delegated;
        BaseRewards base;
        // Reward rate expressed in arpa weis per second
        uint80 rate;
        // Timestamp when the reward stops accumulating. Has to support a very long
        // duration for scenarios with low reward rate.
        // `endTimestamp` >= `startTimestamp`
        uint32 endTimestamp;
        // Timestamp when the reward comes into effect
        // `startTimestamp` <= `endTimestamp`
        uint32 startTimestamp;
    }

    /// @notice initializes the reward with the defined parameters
    /// @param minRewardDuration the minimum duration rewards need to last for
    /// @param newReward the amount of rewards to be added to the pool
    /// @param rewardDuration the duration for which the reward will be distributed
    function _initialize(Reward storage reward, uint256 minRewardDuration, uint256 newReward, uint256 rewardDuration)
        internal
    {
        uint32 blockTimestamp = block.timestamp._toUint32();
        reward.startTimestamp = blockTimestamp;

        reward.delegated.lastAccumulateTimestamp = blockTimestamp;
        reward.base.lastAccumulateTimestamp = blockTimestamp;

        _updateReward(reward, newReward, rewardDuration, minRewardDuration);

        emit RewardInitialized(newReward, reward.startTimestamp, reward.endTimestamp);
    }

    /// @return bool true if the reward is expired (end <= now)
    function _isDepleted(Reward storage reward) internal view returns (bool) {
        return reward.endTimestamp <= block.timestamp;
    }

    /// @notice Helper function to accumulate base rewards
    /// Accumulate reward per micro ARPA before changing reward rate.
    /// This keeps rewards prior to rate change unaffected.
    function _accumulateBaseRewards(Reward storage reward, uint256 totalStakedAmount) internal {
        reward.base.cumulativePerShare = _calculateCumulativeBaseRewards(reward, totalStakedAmount)._toUint96();
        reward.base.lastAccumulateTimestamp = _getCappedTimestamp(reward)._toUint32();
    }

    /// @notice Helper function to accumulate delegation rewards
    /// @dev This function is necessary to correctly account for any changes in
    /// eligible operators, delegated amount or reward rate.
    function _accumulateDelegationRewards(
        Reward storage reward,
        uint256 totalDelegatedAmount,
        uint256 totalStakedAmount
    ) internal {
        reward.delegated.cumulativePerDelegate =
            _calculateCumulativeDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount)._toUint96();
        reward.delegated.lastAccumulateTimestamp = _getCappedTimestamp(reward)._toUint32();
    }

    function _calculateCumulativeBaseRewards(Reward storage reward, uint256 totalStakedAmount)
        internal
        view
        returns (uint256)
    {
        if (totalStakedAmount == 0) return reward.base.cumulativePerShare;
        uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
            ? (uint256(reward.endTimestamp) - uint256(reward.base.lastAccumulateTimestamp))
            : block.timestamp - uint256(reward.base.lastAccumulateTimestamp);

        return reward.base.cumulativePerShare
            + (uint256(reward.rate) * elapsedDurationSinceLastAccumulate * REWARD_PRECISION / totalStakedAmount)._toUint96();
    }

    function _calculateCumulativeDelegatedRewards(
        Reward storage reward,
        uint256 totalDelegatedAmount,
        uint256 totalStakedAmount
    ) internal view returns (uint256) {
        if (totalStakedAmount == 0) return reward.delegated.cumulativePerDelegate;
        uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
            ? uint256(reward.endTimestamp) - uint256(reward.delegated.lastAccumulateTimestamp)
            : block.timestamp - uint256(reward.delegated.lastAccumulateTimestamp);

        return reward.delegated.cumulativePerDelegate
            + (
                uint256(reward.rate) * elapsedDurationSinceLastAccumulate * totalDelegatedAmount / totalStakedAmount
                    / Math.max(uint256(reward.delegated.delegatesCount), 1)
            )._toUint96();
    }

    /// @notice Calculates the amount of delegated rewards accumulated so far.
    /// @dev This function takes into account the amount of delegated
    /// rewards accumulated from previous delegate counts and amounts and
    /// the latest additional value.
    function _calculateAccruedDelegatedRewards(
        Reward storage reward,
        uint256 totalDelegatedAmount,
        uint256 totalStakedAmount
    ) internal view returns (uint256) {
        return _calculateCumulativeDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount);
    }

    /// @notice Calculates the amount of rewards accrued so far.
    /// @dev This function takes into account the amount of
    /// rewards accumulated from previous rates in addition to
    /// the rewards that will be accumulated based off the current rate
    /// over a given duration.
    function _calculateAccruedBaseRewards(Reward storage reward, uint256 amount, uint256 totalStakedAmount)
        internal
        view
        returns (uint256)
    {
        return amount * _calculateCumulativeBaseRewards(reward, totalStakedAmount) / REWARD_PRECISION;
    }

    /// @notice calculates an amount that community stakers have to delegate to operators
    /// @param amount base staked amount to calculate delegated amount against
    /// @param delegationRateDenominator Delegation rate used to calculate delegated stake amount
    function _getDelegatedAmount(uint256 amount, uint256 delegationRateDenominator) internal pure returns (uint256) {
        return amount / delegationRateDenominator;
    }

    /// @notice calculates the amount of stake that remains after accounting for delegation requirement
    /// @param amount base staked amount to calculate non-delegated amount against
    /// @param delegationRateDenominator Delegation rate used to calculate delegated stake amount
    function _getNonDelegatedAmount(uint256 amount, uint256 delegationRateDenominator)
        internal
        pure
        returns (uint256)
    {
        return amount - _getDelegatedAmount(amount, delegationRateDenominator);
    }

    /// @notice This function is called when the staking pool is initialized,
    /// rewards are added, TODO and an alert is raised
    /// @param newReward new reward amount
    /// @param rewardDuration duration of the reward
    function _updateReward(Reward storage reward, uint256 newReward, uint256 rewardDuration, uint256 minRewardDuration)
        internal
    {
        uint256 remainingRewards =
            (_isDepleted(reward) ? 0 : (reward.rate * (uint256(reward.endTimestamp) - block.timestamp))) + newReward;

        // Validate that the new reward duration is at least the min reward duration.
        // This is a safety mechanism to guard against operational mistakes.
        if (rewardDuration < minRewardDuration) {
            revert RewardDurationTooShort();
        }

        reward.endTimestamp = (block.timestamp + rewardDuration)._toUint32();
        reward.rate = (remainingRewards / rewardDuration)._toUint80();
    }

    /// @return The amount of delegated rewards an operator
    /// has earned.
    function _getOperatorEarnedDelegatedRewards(
        Reward storage reward,
        address operator,
        uint256 totalDelegatedAmount,
        uint256 totalStakedAmount
    ) internal view returns (uint256) {
        return _calculateAccruedDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount)
            - uint256(reward.missed[operator].delegated);
    }

    /// @return The current timestamp or, if the current timestamp has passed reward
    /// end timestamp, reward end timestamp.
    /// @dev This is necessary to ensure that rewards are calculated correctly
    /// after the reward is depleted.
    function _getCappedTimestamp(Reward storage reward) internal view returns (uint256) {
        return Math.min(uint256(reward.endTimestamp), block.timestamp);
    }
}

File 14 of 20 : IMigrationTarget.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IMigrationTarget {
    /// @notice This function allows stakers to migrate funds from an old staking pool.
    /// @param amount Amount of tokens to migrate
    /// @param data Migration path details
    function migrateFrom(uint256 amount, bytes calldata data) external;
}

File 15 of 20 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

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

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

File 16 of 20 : draft-IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

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

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

File 17 of 20 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @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
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 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 functionCallWithValue(target, data, 0, "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");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, 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) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, 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) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or 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 {
            _revert(returndata, errorMessage);
        }
    }

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

File 18 of 20 : 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;
    }
}

File 19 of 20 : IERC165.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 IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 20 of 20 : SafeCast.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

library SafeCast {
    error CastError();

    /// @notice This is used to safely case timestamps to uint8
    uint256 private constant MAX_UINT_8 = type(uint8).max;
    /// @notice This is used to safely case timestamps to uint32
    uint256 private constant MAX_UINT_32 = type(uint32).max;
    /// @notice This is used to safely case timestamps to uint80
    uint256 private constant MAX_UINT_80 = type(uint80).max;
    /// @notice This is used to safely case timestamps to uint96
    uint256 private constant MAX_UINT_96 = type(uint96).max;

    function _toUint8(uint256 value) internal pure returns (uint8) {
        if (value > MAX_UINT_8) revert CastError();
        return uint8(value);
    }

    function _toUint32(uint256 value) internal pure returns (uint32) {
        if (value > MAX_UINT_32) revert CastError();
        return uint32(value);
    }

    function _toUint80(uint256 value) internal pure returns (uint80) {
        if (value > MAX_UINT_80) revert CastError();
        return uint80(value);
    }

    function _toUint96(uint256 value) internal pure returns (uint96) {
        if (value > MAX_UINT_96) revert CastError();
        return uint96(value);
    }
}

Settings
{
  "remappings": [
    "Staking-v0.1/=lib/Staking-v0.1/src/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 500
  },
  "metadata": {
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "libraries": {
    "src/libraries/BLS.sol": {
      "BLS": "0x25e627ED5C1102C4a130e8B846aA24867898Eb78"
    },
    "src/libraries/GroupLib.sol": {
      "GroupLib": "0xDa08c1Be1519C3AdF6a71b4b0634208e02eccDC9"
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"components":[{"internalType":"contract IERC20","name":"arpa","type":"address"},{"internalType":"uint256","name":"initialMaxPoolSize","type":"uint256"},{"internalType":"uint256","name":"initialMaxCommunityStakeAmount","type":"uint256"},{"internalType":"uint256","name":"minCommunityStakeAmount","type":"uint256"},{"internalType":"uint256","name":"operatorStakeAmount","type":"uint256"},{"internalType":"uint256","name":"minInitialOperatorCount","type":"uint256"},{"internalType":"uint256","name":"minRewardDuration","type":"uint256"},{"internalType":"uint256","name":"delegationRateDenominator","type":"uint256"},{"internalType":"uint256","name":"unstakeFreezingDuration","type":"uint256"}],"internalType":"struct Staking.PoolConstructorParams","name":"params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessForbidden","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CastError","type":"error"},{"inputs":[{"internalType":"uint256","name":"remainingAmount","type":"uint256"}],"name":"ExcessiveStakeAmount","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"ExistingLockedStakeFound","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"ExistingStakeFound","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"FrozenPrincipalDoesNotExist","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"minInitialOperatorsCount","type":"uint256"}],"name":"InadequateInitialOperatorsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentLockedStakingAmount","type":"uint256"}],"name":"InadequateOperatorLockedStakingAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentStakingAmount","type":"uint256"}],"name":"InadequateStakingAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"requiredAmount","type":"uint256"}],"name":"InsufficientStakeAmount","type":"error"},{"inputs":[],"name":"InvalidDelegationRate","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxStakeAmount","type":"uint256"}],"name":"InvalidMaxStakeAmount","type":"error"},{"inputs":[],"name":"InvalidMigrationTarget","type":"error"},{"inputs":[],"name":"InvalidMinCommunityStakeAmount","type":"error"},{"inputs":[],"name":"InvalidOperatorStakeAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxPoolSize","type":"uint256"}],"name":"InvalidPoolSize","type":"error"},{"inputs":[{"internalType":"bool","name":"currentStatus","type":"bool"},{"internalType":"bool","name":"requiredStatus","type":"bool"}],"name":"InvalidPoolStatus","type":"error"},{"inputs":[],"name":"InvalidZeroAddress","type":"error"},{"inputs":[],"name":"NoBaseRewardForOperator","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"OperatorAlreadyExists","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"OperatorDoesNotExist","type":"error"},{"inputs":[],"name":"RewardDurationTooShort","type":"error"},{"inputs":[],"name":"SenderNotController","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"StakeNotFound","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"UnstakeOperatorWithPartialAmount","type":"error"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"UnstakeWithZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"controller","type":"address"}],"name":"ControllerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"DelegationRewardSlashed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"principal","type":"uint256"}],"name":"FrozenPrincipalClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newLock","type":"uint256"}],"name":"Locked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"principal","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseReward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegationReward","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Migrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"migrationTarget","type":"address"}],"name":"MigrationTargetAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"migrationTarget","type":"address"}],"name":"MigrationTargetProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"baseReward","type":"uint256"}],"name":"RewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newStake","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalStake","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"arpaAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"initialMaxPoolSize","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"initialMaxCommunityStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minCommunityStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"operatorStakeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minInitialOperatorCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minRewardDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegationRateDenominator","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeFreezingDuration","type":"uint256"}],"name":"StakingConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newUnlock","type":"uint256"}],"name":"Unlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"principal","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseReward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"delegationReward","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"acceptMigrationTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"operators","type":"address[]"}],"name":"addOperators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"addReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimFrozenPrincipal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyUnpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getArpaToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAvailableReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getBaseReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"getClaimablePrincipalAmount","outputs":[{"internalType":"uint256","name":"claimingPrincipal","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCommunityStakerLimits","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCommunityStakersCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getController","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDelegatesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDelegationRateDenominator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getDelegationReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getFrozenPrincipal","outputs":[{"internalType":"uint96[]","name":"amounts","type":"uint96[]"},{"internalType":"uint256[]","name":"unlockTimestamps","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getLockedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxPoolSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMigrationTarget","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOperatorLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardTimestamps","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalCommunityStakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalDelegatedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFrozenAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalStakedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"isOperator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"newReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"migrationTarget","type":"address"}],"name":"proposeMigrationTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"setController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxPoolSize","type":"uint256"},{"internalType":"uint256","name":"maxCommunityStakeAmount","type":"uint256"}],"name":"setPoolConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"slashDelegationReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDuration","type":"uint256"}],"name":"start","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6101606040523480156200001257600080fd5b5060405162004929380380620049298339810160408190526200003591620004b8565b62000040336200023f565b6000805460ff60a01b1916905580516001600160a01b0316620000765760405163f6b2911f60e01b815260040160405180910390fd5b8060e001516000036200009c5760405163027953ef60e61b815260040160405180910390fd5b60008160e0015164e8d4a51000620000b591906200053f565b1115620000d55760405163027953ef60e61b815260040160405180910390fd5b8060800151600003620000fa5760405162c4149d60e51b815260040160405180910390fd5b806040015181606001511115620001245760405163941b857f60e01b815260040160405180910390fd5b620001498160200151826040015160016200028f60201b62001f1c179092919060201c565b80516001600160a01b031660809081528101805160a090815260608301805160c0908152918401805160e09081529285018051610100908152938601805161012052938601805161014052865160208801516040808a015196519851955194519751935190517f4f5fcb1be0e233ce19a3f1a9544465fca14a2e18bb8d6e4821ed92d0c6272b3a996200023099949893979396909593949093909290916001600160a01b03999099168952602089019790975260408801959095526060870193909352608086019190915260a085015260c084015260e08301526101008201526101200190565b60405180910390a15062000562565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60028301546001600160601b0316821015620002c657604051630f9e1c3b60e11b8152600481018390526024015b60405180910390fd5b60028301546c0100000000000000000000000090046001600160601b0316811015620003095760405163bc91aa3360e01b815260048101829052602401620002bd565b60028301546001600160601b031682146200038a5762000334826200043460201b620020831760201c565b6002840180546001600160601b0319166001600160601b03929092169190911790556040518281527f7f4f497e086b2eb55f8a9885ba00d33399bbe0ebcb92ea092834386435a1b9c09060200160405180910390a15b60028301546c0100000000000000000000000090046001600160601b031681146200042f57620003c5816200043460201b620020831760201c565b6002840180546001600160601b03929092166c0100000000000000000000000002600160601b600160c01b03199092169190911790556040518181527fb5f554e5ef00806bace1edbb84186512ebcefa2af7706085143f501f29314df79060200160405180910390a15b505050565b60006001600160601b038211156200045f5760405163408ba96f60e11b815260040160405180910390fd5b5090565b60405161012081016001600160401b03811182821017156200049557634e487b7160e01b600052604160045260246000fd5b60405290565b80516001600160a01b0381168114620004b357600080fd5b919050565b60006101208284031215620004cc57600080fd5b620004d662000463565b620004e1836200049b565b81526020830151602082015260408301516040820152606083015160608201526080830151608082015260a083015160a082015260c083015160c082015260e083015160e08201526101008084015181830152508091505092915050565b6000826200055d57634e487b7160e01b600052601260045260246000fd5b500690565b60805160a05160c05160e0516101005161012051610140516142cb6200065e6000396000818161242801526127050152600081816104f6015281816114fd01528181611682015281816117a20152818161254f015281816127a30152613649015260008181610cce01526113ce0152600061136e0152600081816103500152818161352001526135550152600081816104150152818161227001528181613296015281816132cb015281816132f901526133240152600081816106bc0152818161090b01528181610c9d0152818161115b0152818161139f0152818161163f01528181611834015281816119120152611bac01526142cb6000f3fe608060405234801561001057600080fd5b50600436106103205760003560e01c80637a766460116101a7578063a694fc3a116100ee578063e8376b8a11610097578063ed63e80711610071578063ed63e80714610700578063f2fde38b1461070e578063fa4934e71461072157600080fd5b8063e8376b8a146106e8578063e937fdaa146106f0578063e9f37cdf146106f857600080fd5b8063cfcd8fd8116100c8578063cfcd8fd8146106a7578063d174e658146106ba578063e0974ea5146106e057600080fd5b8063a694fc3a14610684578063a7a2f5aa14610697578063b88a802f1461069f57600080fd5b80638da5cb5b1161015057806392eefe9b1161012a57806392eefe9b1461064b5780639a109bc21461065e578063a07aea1c1461067157600080fd5b80638da5cb5b146105f25780638fb4b57314610603578063929ec5371461061657600080fd5b806387e900b11161018157806387e900b1146105b95780638899fdeb146105cc5780638932a90d146105df57600080fd5b80637a7664601461055b5780637e1a3786146105925780637eee288d146105a657600080fd5b80633ff089851161026b5780635c975abb116102145780636d70f7ae116101ee5780636d70f7ae1461052d578063715018a61461054057806375c93bb91461054857600080fd5b80635c975abb146104e25780635e8b40d7146104f457806363b2c85a1461051a57600080fd5b80634e71d92d116102455780634e71d92d146104b357806351858e27146104bb57806359f01879146104c357600080fd5b80633ff08985146104855780634a4e3bd5146104985780634aba2ca9146104a057600080fd5b80632624c83c116102cd5780633018205f116102a75780633018205f1461046157806332e288501461047257806338adb6f01461047d57600080fd5b80632624c83c14610413578063282d3fdf146104395780632e17de781461044e57600080fd5b8063181f5a77116102fe578063181f5a77146103a75780631ddb5552146103d657806322f3e2d4146103fb57600080fd5b8063049b2ca0146103255780630641bdd81461034b5780630fbc8f5b14610396575b600080fd5b6002546201000090046001600160601b03165b6040519081526020015b60405180910390f35b6003547f000000000000000000000000000000000000000000000000000000000000000090600160601b90046001600160601b03165b60408051928352602083019190915201610342565b6003546001600160601b0316610338565b604080518082018252600d81526c05374616b696e6720302e312e3609c1b602082015290516103429190613df1565b600c546001600160a01b03165b6040516001600160a01b039091168152602001610342565b610403610742565b6040519015158152602001610342565b7f0000000000000000000000000000000000000000000000000000000000000000610338565b61044c610447366004613e20565b61076d565b005b61044c61045c366004613e4a565b6108b1565b6009546001600160a01b03166103e3565b60065460ff16610338565b610338610980565b610338610493366004613e63565b61098c565b61044c610a45565b61044c6104ae366004613e7e565b610a57565b61044c610a77565b61044c610aa8565b60085463ffffffff600160701b8204811691600160501b900416610381565b600054600160a01b900460ff16610403565b7f0000000000000000000000000000000000000000000000000000000000000000610338565b61044c610528366004613e63565b610ab8565b61040361053b366004613e63565b610c06565b61044c610c29565b61044c610556366004613e7e565b610c3b565b610338610569366004613e63565b6001600160a01b031660009081526001602052604090205461010090046001600160601b031690565b60085469ffffffffffffffffffff16610338565b61044c6105b4366004613e20565b610d3a565b6103386105c7366004613e63565b610e78565b61044c6105da366004613e20565b610f9f565b61044c6105ed366004613ea0565b6111cc565b6000546001600160a01b03166103e3565b61044c610611366004613e7e565b611332565b610338610624366004613e63565b6001600160a01b03166000908152600160205260409020600201546001600160601b031690565b61044c610659366004613e63565b6113f4565b61033861066c366004613e63565b611471565b61044c61067f366004613f12565b611547565b61044c610692366004613e4a565b6115a6565b610338611667565b61044c6116a6565b61044c6106b5366004613e7e565b611891565b7f00000000000000000000000000000000000000000000000000000000000000006103e3565b6103386118ef565b61044c611999565b61044c611c09565b600454610338565b60075463ffffffff16610338565b61044c61071c366004613e63565b611cc9565b61073461072f366004613e63565b611d42565b604051610342929190613f75565b60025460009060ff168015610768575060085442600160501b90910463ffffffff161115155b905090565b6009546001600160a01b0316331461079857604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b0382166000908152600160205260409020805460ff166107e25760405163eac13dcd60e01b81526001600160a01b03841660048201526024015b60405180910390fd5b805461010090046001600160601b031682111561081557604051631d820b1760e01b8152600481018390526024016107d9565b61081e82612083565b60028201805460009061083b9084906001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055507f9f1ec8c880f76798e7b793325d625e9b60e4082a553c98f42b6cda368dd6000883836040516108a49291906001600160a01b03929092168252602082015260400190565b60405180910390a1505050565b6108b96120b1565b60006108ca64e8d4a510008361404c565b905080156108df576108dc8183614060565b91505b6000806108ee3385600061210b565b9092509050610932336109018385614073565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169190612817565b6040805133815260208101869052908101839052606081018290527f204fccf0d92ed8d48f204adb39b2e81e92bad0dedb93f5716ca9478cfb57de009060800160405180910390a150505050565b600061076860016128a7565b336000908152600160208190526040822001805482036109af5750600092915050565b60005b8154811015610a3e5760008282815481106109cf576109cf614086565b90600052602060002090600202019050428383815481106109f2576109f2614086565b90600052602060002090600202016001015411610a25578054610a1e906001600160601b031685614073565b9350610a2b565b50610a3e565b5080610a368161409c565b9150506109b2565b5050919050565b610a4d6128fd565b610a55612957565b565b610a5f6128fd565b610a676129a7565b610a7360018383611f1c565b5050565b610a7f6120b1565b610a876116a6565b336000908152600160208190526040909120015415610a5557610a55611999565b610ab06128fd565b610a556129d6565b610ac06128fd565b6001600160a01b0381163b1580610adf57506001600160a01b03811630145b80610af75750600a546001600160a01b038281169116145b80610b0f5750600c546001600160a01b038281169116145b80610b8657506040516301ffc9a760e01b81526331f5be1560e01b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa158015610b60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8491906140b5565b155b15610ba4576040516306cf420760e31b815260040160405180910390fd5b600c80546001600160a01b0319908116909155600a80546001600160a01b03841692168217905542600b556040519081527f5c74c441be501340b2713817a6c6975e6f3d4a4ae39fa1ac0bf75d3c54a0cad3906020015b60405180910390a150565b6001600160a01b03811660009081526001602052604081205460ff165b92915050565b610c316128fd565b610a556000612a19565b610c436128fd565b610c4b6129a7565b600254610c6a906201000090046001600160601b03165b600590612a69565b610c90610c75611667565b6002546201000090046001600160601b031660059190612ae7565b610cc56001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016333085612b5c565b610cf2600583837f0000000000000000000000000000000000000000000000000000000000000000612b9a565b7f6c07ee05dcf262f13abf9d87b846ee789d2f90fe991d495acd7d7fc109ee1f5582610d1e8342614073565b6040805192835260208301919091520160405180910390a15050565b6009546001600160a01b03163314610d6557604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b0382166000908152600160205260409020805460ff16610daa5760405163eac13dcd60e01b81526001600160a01b03841660048201526024016107d9565b60028101546001600160601b0316821115610de957600281015460405163c9dcab8760e01b81526001600160601b0390911660048201526024016107d9565b610df282612083565b600282018054600090610e0f9084906001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b031602179055507f0f0bc5b519ddefdd8e5f9e6423433aa2b869738de2ae34d58ebc796fc749fa0d83836040516108a49291906001600160a01b03929092168252602082015260400190565b6001600160a01b03811660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152879695939486019390929190879084015b82821015610f23576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101610edb565b50505090825250600291909101546001600160601b03166020909101528051909150610f525750600092915050565b80602001516001600160601b0316600003610f705750600092915050565b610f9883610f7c611667565b6002546201000090046001600160601b03166005929190612ca2565b9392505050565b6009546001600160a01b03163314610fca57604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b03821660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152929493860193879084015b8282101561106f576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611027565b50505090825250600291909101546001600160601b031660209091015280519091506110b95760405163eac13dcd60e01b81526001600160a01b03841660048201526024016107d9565b60006110c784610f7c611667565b905060006110d58483612ceb565b90506110e081612083565b6001600160a01b03861660009081526005602052604090208054600c90611118908490600160601b90046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506111826111516000546001600160a01b031690565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169083612817565b604080516001600160a01b0387168152602081018390527ff3136d14e8fd8ddb090effdbecb5d9547b6e17d9006ef9c25ca5b1e00dbfe51791015b60405180910390a15050505050565b6111d4610742565b156111fc57604051635185386160e11b815260016004820152600060248201526044016107d9565b600c546001600160a01b0316611225576040516306cf420760e31b815260040160405180910390fd5b600080600061123333612d01565b600c549295509093509150611260906001600160a01b0316826112568587614073565b6109019190614073565b600c546001600160a01b03166331f5be158261127c8587614073565b6112869190614073565b33888860405160200161129b93929190614120565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016112c7929190614143565b600060405180830381600087803b1580156112e157600080fd5b505af11580156112f5573d6000803e3d6000fd5b505050507f667838b33bdc898470de09e0e746990f2adc11b965b7fe6828e502ebc39e04343384848489896040516111bd9695949392919061415c565b61133a6128fd565b600854600160701b900463ffffffff16156113675760405162dc149f60e41b815260040160405180910390fd5b61139260017f0000000000000000000000000000000000000000000000000000000000000000612e72565b6113c76001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016333085612b5c565b610a7360057f00000000000000000000000000000000000000000000000000000000000000008484612ef2565b6113fc6128fd565b6001600160a01b0381166114235760405163f6b2911f60e01b815260040160405180910390fd5b600980546001600160a01b0319166001600160a01b0383169081179091556040519081527f79f74fd5964b6943d8a1865abfb7f668c92fa3f32c0a2e3195da7d0946703ad790602001610bfb565b6001600160a01b03811660009081526001602052604081205461010090046001600160601b03168082036114a85750600092915050565b6001600160a01b03831660009081526001602052604090205460ff16156114d25750600092915050565b6001600160a01b0383166000908152600560205260409020546001600160601b031661153d611521837f0000000000000000000000000000000000000000000000000000000000000000612fcc565b6002546201000090046001600160601b03165b60059190612fe2565b610f989190614060565b61154f6128fd565b600854600160701b900463ffffffff16158015906115725750611570610742565b155b1561159a57604051635185386160e11b815260006004820152600160248201526044016107d9565b610a7360018383613008565b6115ae6120b1565b64e8d4a510008110156115db57604051631d820b1760e01b815264e8d4a5100060048201526024016107d9565b60006115ec64e8d4a510008361404c565b90508015611601576115fe8183614060565b91505b3360009081526001602052604090205460ff161561162857611623338361325c565b611632565b61163233836134e2565b610a736001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016333085612b5c565b600254600090610768906201000090046001600160601b03167f00000000000000000000000000000000000000000000000000000000000000006137c8565b6116ae6120b1565b3360009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152929493860193879084015b8282101561174a576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611702565b50505090825250600291909101546001600160601b031660209091015280519091501561178a5760405163b7195bcb60e01b815260040160405180910390fd5b60006117c661152183602001516001600160601b03167f0000000000000000000000000000000000000000000000000000000000000000612fcc565b33600090815260056020526040812054919250906117ed906001600160601b031683614060565b90506117f882612083565b33600081815260056020526040902080546001600160601b0319166001600160601b03939093169290921790915561185b906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169083612817565b60408051338152602081018390527f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f724191016108a4565b6118996128fd565b6118a1610742565b156118c957604051635185386160e11b815260016004820152600060248201526044016107d9565b6002546118e4906201000090046001600160601b0316610c62565b611392610c75611667565b6004546000906118fd610980565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611961573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611985919061419d565b61198f9190614060565b6107689190614060565b6119a16120b1565b33600090815260016020819052604082200180549091036119d757604051630f1ed8ad60e21b81523360048201526024016107d9565b60008060005b8354811015611a9d5760008482815481106119fa576119fa614086565b9060005260206000209060020201905042858381548110611a1d57611a1d614086565b90600052602060002090600202016001015411611a84578054611a49906001600160601b031685614073565b8154600480549296506001600160601b0390911691600090611a6c908490614060565b90915550839050611a7c8161409c565b935050611a8a565b50611a9d565b5080611a958161409c565b9150506119dd565b508015611b995760005b8354611ab4908390614060565b811015611b405783611ac68383614073565b81548110611ad657611ad6614086565b9060005260206000209060020201848281548110611af657611af6614086565b60009182526020909120825460029092020180546001600160601b0319166001600160601b0390921691909117815560019182015491015580611b388161409c565b915050611aa7565b5060005b81811015611b975783805480611b5c57611b5c6141b6565b60008281526020812060026000199093019283020180546001600160601b031916815560010155905580611b8f8161409c565b915050611b44565b505b8115611bd357611bd36001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163384612817565b60408051338152602081018490527fbb6ad7a7bb63a1746b89ec2f2339de4e0f983ef7e7b101c8c4f716c08c2a711491016108a4565b611c116128fd565b600a546001600160a01b0316611c3a576040516306cf420760e31b815260040160405180910390fd5b600b54611c4a9062093a80614073565b421015611c6a57604051631decfebb60e31b815260040160405180910390fd5b600a8054600c80546001600160a01b0383166001600160a01b031991821681179092559091169091556040519081527ffa33c052bbee754f3c0482a89962daffe749191fa33c696a61e947fbfd68bd84906020015b60405180910390a1565b611cd16128fd565b6001600160a01b038116611d365760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016107d9565b611d3f81612a19565b50565b606080600060016000016000856001600160a01b03166001600160a01b03168152602001908152602001600020600101805480602002602001604051908101604052809291908181526020016000905b82821015611dda576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611d92565b505050509050805167ffffffffffffffff811115611dfa57611dfa6141cc565b604051908082528060200260200182016040528015611e23578160200160208202803683370190505b509250805167ffffffffffffffff811115611e4057611e406141cc565b604051908082528060200260200182016040528015611e69578160200160208202803683370190505b50915060005b8151811015611f1557818181518110611e8a57611e8a614086565b602002602001015160000151848281518110611ea857611ea8614086565b60200260200101906001600160601b031690816001600160601b031681525050818181518110611eda57611eda614086565b602002602001015160200151838281518110611ef857611ef8614086565b602090810291909101015280611f0d8161409c565b915050611e6f565b5050915091565b60028301546001600160601b0316821015611f4d57604051630f9e1c3b60e11b8152600481018390526024016107d9565b6002830154600160601b90046001600160601b0316811015611f855760405163bc91aa3360e01b8152600481018290526024016107d9565b60028301546001600160601b03168214611ff857611fa282612083565b6002840180546001600160601b0319166001600160601b03929092169190911790556040518281527f7f4f497e086b2eb55f8a9885ba00d33399bbe0ebcb92ea092834386435a1b9c09060200160405180910390a15b6002830154600160601b90046001600160601b0316811461207e5761201c81612083565b6002840180546001600160601b0392909216600160601b026bffffffffffffffffffffffff60601b199092169190911790556040518181527fb5f554e5ef00806bace1edbb84186512ebcefa2af7706085143f501f29314df7906020016108a4565b505050565b60006001600160601b038211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b5090565b600054600160a01b900460ff1615610a555760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016107d9565b6001600160a01b03831660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152879687969095860193919290879084015b828210156121b6576000848152602090819020604080518082019091526002850290910180546001600160601b0316825260019081015482840152908352909201910161216e565b50505090825250600291909101546001600160601b03166020909101529050600085900361220257604051637ece672b60e11b81526001600160a01b03871660048201526024016107d9565b8481602001516001600160601b0316101561224157602081015160405163477d28dd60e01b81526001600160601b0390911660048201526024016107d9565b60025461225c906201000090046001600160601b0316610c62565b612267610c75611667565b805115612519577f000000000000000000000000000000000000000000000000000000000000000085146122b957604051633ac3109f60e01b81526001600160a01b03871660048201526024016107d9565b60608101516001600160601b0316156122f057604051634ed4c4c760e11b81526001600160a01b03871660048201526024016107d9565b60006122fe87610f7c611667565b905061230986612083565b60028054600e9061232b908490600160701b90046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061235886612083565b6001600160a01b0388166000908152600160208190526040909120805490919061239190849061010090046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555084612491576123c386612083565b6001600160601b0316600160030160008282546123e09190614073565b90915550506001600160a01b038716600090815260016020819052604091829020825180840190935201908061241589612083565b6001600160601b0316815260200161244d7f000000000000000000000000000000000000000000000000000000000000000042614073565b90528154600180820184556000938452602093849020835160029093020180546001600160601b0319166001600160601b0390931692909217825591909201519101555b60068054600191906000906124aa90849060ff166141e2565b825461010092830a60ff818102199092169290911602179091556006546001600160a01b038a16600090815260056020526040812080546bffffffffffffffffffffffff60601b1916939092046001600160601b0316600160601b02929092179055945090925061280f915050565b6001600160a01b0386166000908152600560209081526040822054908301516001600160601b03918216916125739161152191167f0000000000000000000000000000000000000000000000000000000000000000612fcc565b61257d9190614060565b905061258886612083565b6002805481906125a89084906201000090046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506125d586612083565b6001600160a01b0388166000908152600160208190526040909120805490919061260e90849061010090046001600160601b03166140d7565b82546001600160601b0391821661010093840a90810290830219909116179092556001600160a01b038a16600090815260016020526040812054919091049091169003905061269257600780546001919060009061267390849063ffffffff166141fb565b92506101000a81548163ffffffff021916908363ffffffff1602179055505b8461276e576126a086612083565b6001600160601b0316600160030160008282546126bd9190614073565b90915550506001600160a01b03871660009081526001602081905260409182902082518084019093520190806126f289612083565b6001600160601b0316815260200161272a7f000000000000000000000000000000000000000000000000000000000000000042614073565b90528154600180820184556000938452602093849020835160029093020180546001600160601b0319166001600160601b0390931692909217825591909201519101555b6001600160a01b0387166000908152600160205260409020546127cc906127c7906115219061010090046001600160601b03167f0000000000000000000000000000000000000000000000000000000000000000612fcc565b612083565b6001600160a01b038816600090815260056020526040812080546001600160601b0319166001600160601b039390931692909217909155909350915061280f9050565b935093915050565b6040516001600160a01b03831660248201526044810182905261207e90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526137d4565b60408051608081018252600183015460ff8082161515835261010082041660208301526001600160601b036201000082048116938301849052600160701b9091041660608201819052600092610f989190614073565b6000546001600160a01b03163314610a555760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016107d9565b61295f6138a6565b6000805460ff60a01b191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b039091168152602001611cbf565b6129af610742565b610a5557604051635185386160e11b815260006004820152600160248201526044016107d9565b6129de6120b1565b6000805460ff60a01b1916600160a01b1790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25861298f3390565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b612a766127c783836138ff565b6002830180546001600160601b0392909216640100000000026fffffffffffffffffffffffff0000000019909216919091179055612abb612ab6836139ff565b613a1e565b6002909201805463ffffffff93909316600160801b0263ffffffff60801b199093169290921790915550565b612af56127c7848484613a45565b6001840180546001600160601b0392909216610100026cffffffffffffffffffffffff0019909216919091179055612b2f612ab6846139ff565b6001909301805463ffffffff94909416600160681b0263ffffffff60681b19909416939093179092555050565b6040516001600160a01b0380851660248301528316604482015260648101829052612b949085906323b872dd60e01b90608401612843565b50505050565b6003840154600090849042600160501b90910463ffffffff161115612bf6576003860154612bd6904290600160501b900463ffffffff16614060565b6003870154612bf1919069ffffffffffffffffffff16614218565b612bf9565b60005b612c039190614073565b905081831015612c255760405162da056d60e81b815260040160405180910390fd5b612c32612ab68442614073565b60038601805463ffffffff92909216600160501b026dffffffff0000000000000000000019909216919091179055612c72612c6d848361422f565b613b50565b600395909501805469ffffffffffffffffffff191669ffffffffffffffffffff9096169590951790945550505050565b6001600160a01b038316600090815260208590526040812054600160601b90046001600160601b0316612cd6868585613b7d565b612ce09190614060565b90505b949350505050565b6000818310612cfa5781610f98565b5090919050565b6001600160a01b03811660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b031681850152938101805483518186028101860185528181528796879687969195949186019390879084015b82821015612dae576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101612d66565b50505090825250600291909101546001600160601b039081166020928301529082015191925016600003612e0057604051637256ef3960e11b81526001600160a01b03861660048201526024016107d9565b60608101516001600160601b031615612e3757604051634ed4c4c760e11b81526001600160a01b03861660048201526024016107d9565b600080612e538784602001516001600160601b0316600161210b565b6020909401516001600160601b03169650945091925050509193909250565b6001820154610100900460ff16811115612eb557600182015460405163e709379960e01b815261010090910460ff166004820152602481018290526044016107d9565b6001828101805460ff191690911790556040517fded6ebf04e261e1eb2f3e3b268a2e6aee5b478c15b341eba5cf18b9bc80c2e6390600090a15050565b6000612efd42613a1e565b60038601805471ffffffff00000000000000000000000000001916600160701b63ffffffff84169081029190911790915560018701805463ffffffff60681b1916600160681b830217905560028701805463ffffffff60801b1916600160801b9092029190911790559050612f7485848487612b9a565b60038501546040805185815263ffffffff600160701b840481166020830152600160501b909304909216908201527f4398f7e311b2f8164ce7d424166e6e84fd9a74adda069e902beead8e4eb737b8906060016111bd565b6000612fd883836137c8565b610f989084614060565b600064e8d4a51000612ff485846138ff565b612ffe9085614218565b612ce3919061422f565b60005b818110156132185783600084848481811061302857613028614086565b905060200201602081019061303d9190613e63565b6001600160a01b0316815260208101919091526040016000205460ff16156130aa5782828281811061307157613071614086565b90506020020160208101906130869190613e63565b604051625290b360e11b81526001600160a01b0390911660048201526024016107d9565b600084818585858181106130c0576130c0614086565b90506020020160208101906130d59190613e63565b6001600160a01b0316815260208101919091526040016000205461010090046001600160601b0316111561314f5782828281811061311557613115614086565b905060200201602081019061312a9190613e63565b60405163602d4d1160e01b81526001600160a01b0390911660048201526024016107d9565b600184600085858581811061316657613166614086565b905060200201602081019061317b9190613e63565b6001600160a01b031681526020810191909152604001600020805460ff19169115159190911790557fac6fa858e9350a46cec16539926e0fde25b7629f84b5a72bffaae4df888ae86d8383838181106131d6576131d6614086565b90506020020160208101906131eb9190613e63565b6040516001600160a01b03909116815260200160405180910390a1806132108161409c565b91505061300b565b5061322281613b8a565b60018401546132399190610100900460ff16614243565b6001909301805460ff949094166101000261ff0019909416939093179092555050565b6001600160a01b0382166000908152600160205260408120805490916101009091046001600160601b0316906132928483614073565b90507f00000000000000000000000000000000000000000000000000000000000000008110156132f757604051631d820b1760e01b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016107d9565b7f0000000000000000000000000000000000000000000000000000000000000000811115613365576133497f000000000000000000000000000000000000000000000000000000000000000082614060565b604051631728673b60e31b81526004016107d991815260200190565b8160000361340857613378610c75611667565b60065460ff16600081900361339e57600680546cffffffffffffffffffffffff00191690555b6133a9816001614243565b6006805460ff191660ff9290921691909117908190556001600160a01b038716600090815260056020526040902080546bffffffffffffffffffffffff60601b19166101009092046001600160601b0316600160601b02919091179055505b61341184612083565b60028054600e90613433908490600160701b90046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061346081612083565b6001600160a01b03861660008181526001602090815260409182902080546001600160601b0395909516610100026cffffffffffffffffffffffff00199095169490941790935580519182529181018690529081018290527f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee90906060016111bd565b6134ea6129a7565b6001600160a01b03821660009081526001602052604081205461010090046001600160601b03169061351c8383614073565b90507f000000000000000000000000000000000000000000000000000000000000000081101561358157604051631d820b1760e01b81527f000000000000000000000000000000000000000000000000000000000000000060048201526024016107d9565b600354600160601b90046001600160601b0316808211156135a6576133498382614060565b60006135b26001613bae565b9050808511156135d857604051631728673b60e31b8152600481018290526024016107d9565b6002546135f3906201000090046001600160601b0316610c62565b6135fe610c75611667565b8360000361364157600780546001919060009061362290849063ffffffff1661425c565b92506101000a81548163ffffffff021916908363ffffffff1602179055505b600061366d867f0000000000000000000000000000000000000000000000000000000000000000612fcc565b905061368f6127c7826115346002546001600160601b03620100009091041690565b6001600160a01b038816600090815260056020526040812080549091906136c09084906001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506136ed86612083565b60028054819061370d9084906201000090046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061373a84612083565b6001600160a01b03881660008181526001602090815260409182902080546001600160601b0395909516610100026cffffffffffffffffffffffff00199095169490941790935580519182529181018890529081018590527f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee909060600160405180910390a150505050505050565b6000610f98828461422f565b6000613829826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316613c0a9092919063ffffffff16565b80519091501561207e578080602001905181019061384791906140b5565b61207e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016107d9565b600054600160a01b900460ff16610a555760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f742070617573656400000000000000000000000060448201526064016107d9565b6000816000036139255750600282015464010000000090046001600160601b0316610c23565b600383015460009042600160501b90910463ffffffff16111561396357600284015461395e90600160801b900463ffffffff1642614060565b61398e565b6002840154600385015461398e9163ffffffff600160801b909104811691600160501b900416614060565b60038501549091506139ce90849064e8d4a51000906139ba90859069ffffffffffffffffffff16614218565b6139c49190614218565b6127c7919061422f565b60028501546139ee919064010000000090046001600160601b031661400f565b6001600160601b0316949350505050565b6003810154600090610c2390600160501b900463ffffffff1642612ceb565b600063ffffffff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b600081600003613a685750600183015461010090046001600160601b0316610f98565b600384015460009042600160501b90910463ffffffff161115613aa6576001850154613aa190600160681b900463ffffffff1642614060565b613ad1565b60018501546003860154613ad19163ffffffff600160681b909104811691600160501b900416614060565b600180870154919250613b2191613aed9160ff90911690613c19565b600387015485908790613b0d90869069ffffffffffffffffffff16614218565b613b179190614218565b6139c4919061422f565b6001860154613b3e919061010090046001600160601b031661400f565b6001600160601b031695945050505050565b600069ffffffffffffffffffff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b6000612ce3848484613a45565b600060ff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b60408051608081018252600183015460ff8082161515835261010082041660208301526001600160601b036201000082048116938301849052600160701b909104811660608301526002840154600093610f9892909116614060565b6060612ce38484600085613c28565b6000818311612cfa5781610f98565b606082471015613c895760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016107d9565b600080866001600160a01b03168587604051613ca59190614279565b60006040518083038185875af1925050503d8060008114613ce2576040519150601f19603f3d011682016040523d82523d6000602084013e613ce7565b606091505b5091509150613cf887838387613d03565b979650505050505050565b60608315613d72578251600003613d6b576001600160a01b0385163b613d6b5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107d9565b5081612ce3565b612ce38383815115613d875781518083602001fd5b8060405162461bcd60e51b81526004016107d99190613df1565b60005b83811015613dbc578181015183820152602001613da4565b50506000910152565b60008151808452613ddd816020860160208601613da1565b601f01601f19169290920160200192915050565b602081526000610f986020830184613dc5565b80356001600160a01b0381168114613e1b57600080fd5b919050565b60008060408385031215613e3357600080fd5b613e3c83613e04565b946020939093013593505050565b600060208284031215613e5c57600080fd5b5035919050565b600060208284031215613e7557600080fd5b610f9882613e04565b60008060408385031215613e9157600080fd5b50508035926020909101359150565b60008060208385031215613eb357600080fd5b823567ffffffffffffffff80821115613ecb57600080fd5b818501915085601f830112613edf57600080fd5b813581811115613eee57600080fd5b866020828501011115613f0057600080fd5b60209290920196919550909350505050565b60008060208385031215613f2557600080fd5b823567ffffffffffffffff80821115613f3d57600080fd5b818501915085601f830112613f5157600080fd5b813581811115613f6057600080fd5b8660208260051b8501011115613f0057600080fd5b604080825283519082018190526000906020906060840190828701845b82811015613fb75781516001600160601b031684529284019290840190600101613f92565b5050508381038285015284518082528583019183019060005b81811015613fec57835183529284019291840191600101613fd0565b5090979650505050505050565b634e487b7160e01b600052601160045260246000fd5b6001600160601b0381811683821601908082111561402f5761402f613ff9565b5092915050565b634e487b7160e01b600052601260045260246000fd5b60008261405b5761405b614036565b500690565b81810381811115610c2357610c23613ff9565b80820180821115610c2357610c23613ff9565b634e487b7160e01b600052603260045260246000fd5b6000600182016140ae576140ae613ff9565b5060010190565b6000602082840312156140c757600080fd5b81518015158114610f9857600080fd5b6001600160601b0382811682821603908082111561402f5761402f613ff9565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0384168152604060208201526000612ce06040830184866140f7565b828152604060208201526000612ce36040830184613dc5565b6001600160a01b038716815285602082015284604082015283606082015260a06080820152600061419160a0830184866140f7565b98975050505050505050565b6000602082840312156141af57600080fd5b5051919050565b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b60ff8281168282160390811115610c2357610c23613ff9565b63ffffffff82811682821603908082111561402f5761402f613ff9565b8082028115828204841417610c2357610c23613ff9565b60008261423e5761423e614036565b500490565b60ff8181168382160190811115610c2357610c23613ff9565b63ffffffff81811683821601908082111561402f5761402f613ff9565b6000825161428b818460208701613da1565b919091019291505056fea26469706673582212204b33beeb15f45e326d7db3f299b4a8074df7350d41370ad4fcb664fb96c7ff3564736f6c63430008120033000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a000000000000000000000000000000000000000000295be96e640669720000000000000000000000000000000000000000000000000211654585005212800000000000000000000000000000000000000000000000000000000000e8d4a510000000000000000000000000000000000000000000000069e10de76676d08000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000127500

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106103205760003560e01c80637a766460116101a7578063a694fc3a116100ee578063e8376b8a11610097578063ed63e80711610071578063ed63e80714610700578063f2fde38b1461070e578063fa4934e71461072157600080fd5b8063e8376b8a146106e8578063e937fdaa146106f0578063e9f37cdf146106f857600080fd5b8063cfcd8fd8116100c8578063cfcd8fd8146106a7578063d174e658146106ba578063e0974ea5146106e057600080fd5b8063a694fc3a14610684578063a7a2f5aa14610697578063b88a802f1461069f57600080fd5b80638da5cb5b1161015057806392eefe9b1161012a57806392eefe9b1461064b5780639a109bc21461065e578063a07aea1c1461067157600080fd5b80638da5cb5b146105f25780638fb4b57314610603578063929ec5371461061657600080fd5b806387e900b11161018157806387e900b1146105b95780638899fdeb146105cc5780638932a90d146105df57600080fd5b80637a7664601461055b5780637e1a3786146105925780637eee288d146105a657600080fd5b80633ff089851161026b5780635c975abb116102145780636d70f7ae116101ee5780636d70f7ae1461052d578063715018a61461054057806375c93bb91461054857600080fd5b80635c975abb146104e25780635e8b40d7146104f457806363b2c85a1461051a57600080fd5b80634e71d92d116102455780634e71d92d146104b357806351858e27146104bb57806359f01879146104c357600080fd5b80633ff08985146104855780634a4e3bd5146104985780634aba2ca9146104a057600080fd5b80632624c83c116102cd5780633018205f116102a75780633018205f1461046157806332e288501461047257806338adb6f01461047d57600080fd5b80632624c83c14610413578063282d3fdf146104395780632e17de781461044e57600080fd5b8063181f5a77116102fe578063181f5a77146103a75780631ddb5552146103d657806322f3e2d4146103fb57600080fd5b8063049b2ca0146103255780630641bdd81461034b5780630fbc8f5b14610396575b600080fd5b6002546201000090046001600160601b03165b6040519081526020015b60405180910390f35b6003547f000000000000000000000000000000000000000000000000000000e8d4a5100090600160601b90046001600160601b03165b60408051928352602083019190915201610342565b6003546001600160601b0316610338565b604080518082018252600d81526c05374616b696e6720302e312e3609c1b602082015290516103429190613df1565b600c546001600160a01b03165b6040516001600160a01b039091168152602001610342565b610403610742565b6040519015158152602001610342565b7f0000000000000000000000000000000000000000000069e10de76676d0800000610338565b61044c610447366004613e20565b61076d565b005b61044c61045c366004613e4a565b6108b1565b6009546001600160a01b03166103e3565b60065460ff16610338565b610338610980565b610338610493366004613e63565b61098c565b61044c610a45565b61044c6104ae366004613e7e565b610a57565b61044c610a77565b61044c610aa8565b60085463ffffffff600160701b8204811691600160501b900416610381565b600054600160a01b900460ff16610403565b7f0000000000000000000000000000000000000000000000000000000000000014610338565b61044c610528366004613e63565b610ab8565b61040361053b366004613e63565b610c06565b61044c610c29565b61044c610556366004613e7e565b610c3b565b610338610569366004613e63565b6001600160a01b031660009081526001602052604090205461010090046001600160601b031690565b60085469ffffffffffffffffffff16610338565b61044c6105b4366004613e20565b610d3a565b6103386105c7366004613e63565b610e78565b61044c6105da366004613e20565b610f9f565b61044c6105ed366004613ea0565b6111cc565b6000546001600160a01b03166103e3565b61044c610611366004613e7e565b611332565b610338610624366004613e63565b6001600160a01b03166000908152600160205260409020600201546001600160601b031690565b61044c610659366004613e63565b6113f4565b61033861066c366004613e63565b611471565b61044c61067f366004613f12565b611547565b61044c610692366004613e4a565b6115a6565b610338611667565b61044c6116a6565b61044c6106b5366004613e7e565b611891565b7f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a6103e3565b6103386118ef565b61044c611999565b61044c611c09565b600454610338565b60075463ffffffff16610338565b61044c61071c366004613e63565b611cc9565b61073461072f366004613e63565b611d42565b604051610342929190613f75565b60025460009060ff168015610768575060085442600160501b90910463ffffffff161115155b905090565b6009546001600160a01b0316331461079857604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b0382166000908152600160205260409020805460ff166107e25760405163eac13dcd60e01b81526001600160a01b03841660048201526024015b60405180910390fd5b805461010090046001600160601b031682111561081557604051631d820b1760e01b8152600481018390526024016107d9565b61081e82612083565b60028201805460009061083b9084906001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055507f9f1ec8c880f76798e7b793325d625e9b60e4082a553c98f42b6cda368dd6000883836040516108a49291906001600160a01b03929092168252602082015260400190565b60405180910390a1505050565b6108b96120b1565b60006108ca64e8d4a510008361404c565b905080156108df576108dc8183614060565b91505b6000806108ee3385600061210b565b9092509050610932336109018385614073565b6001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a169190612817565b6040805133815260208101869052908101839052606081018290527f204fccf0d92ed8d48f204adb39b2e81e92bad0dedb93f5716ca9478cfb57de009060800160405180910390a150505050565b600061076860016128a7565b336000908152600160208190526040822001805482036109af5750600092915050565b60005b8154811015610a3e5760008282815481106109cf576109cf614086565b90600052602060002090600202019050428383815481106109f2576109f2614086565b90600052602060002090600202016001015411610a25578054610a1e906001600160601b031685614073565b9350610a2b565b50610a3e565b5080610a368161409c565b9150506109b2565b5050919050565b610a4d6128fd565b610a55612957565b565b610a5f6128fd565b610a676129a7565b610a7360018383611f1c565b5050565b610a7f6120b1565b610a876116a6565b336000908152600160208190526040909120015415610a5557610a55611999565b610ab06128fd565b610a556129d6565b610ac06128fd565b6001600160a01b0381163b1580610adf57506001600160a01b03811630145b80610af75750600a546001600160a01b038281169116145b80610b0f5750600c546001600160a01b038281169116145b80610b8657506040516301ffc9a760e01b81526331f5be1560e01b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa158015610b60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8491906140b5565b155b15610ba4576040516306cf420760e31b815260040160405180910390fd5b600c80546001600160a01b0319908116909155600a80546001600160a01b03841692168217905542600b556040519081527f5c74c441be501340b2713817a6c6975e6f3d4a4ae39fa1ac0bf75d3c54a0cad3906020015b60405180910390a150565b6001600160a01b03811660009081526001602052604081205460ff165b92915050565b610c316128fd565b610a556000612a19565b610c436128fd565b610c4b6129a7565b600254610c6a906201000090046001600160601b03165b600590612a69565b610c90610c75611667565b6002546201000090046001600160601b031660059190612ae7565b610cc56001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a16333085612b5c565b610cf2600583837f0000000000000000000000000000000000000000000000000000000000015180612b9a565b7f6c07ee05dcf262f13abf9d87b846ee789d2f90fe991d495acd7d7fc109ee1f5582610d1e8342614073565b6040805192835260208301919091520160405180910390a15050565b6009546001600160a01b03163314610d6557604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b0382166000908152600160205260409020805460ff16610daa5760405163eac13dcd60e01b81526001600160a01b03841660048201526024016107d9565b60028101546001600160601b0316821115610de957600281015460405163c9dcab8760e01b81526001600160601b0390911660048201526024016107d9565b610df282612083565b600282018054600090610e0f9084906001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b031602179055507f0f0bc5b519ddefdd8e5f9e6423433aa2b869738de2ae34d58ebc796fc749fa0d83836040516108a49291906001600160a01b03929092168252602082015260400190565b6001600160a01b03811660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152879695939486019390929190879084015b82821015610f23576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101610edb565b50505090825250600291909101546001600160601b03166020909101528051909150610f525750600092915050565b80602001516001600160601b0316600003610f705750600092915050565b610f9883610f7c611667565b6002546201000090046001600160601b03166005929190612ca2565b9392505050565b6009546001600160a01b03163314610fca57604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b03821660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152929493860193879084015b8282101561106f576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611027565b50505090825250600291909101546001600160601b031660209091015280519091506110b95760405163eac13dcd60e01b81526001600160a01b03841660048201526024016107d9565b60006110c784610f7c611667565b905060006110d58483612ceb565b90506110e081612083565b6001600160a01b03861660009081526005602052604090208054600c90611118908490600160601b90046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506111826111516000546001600160a01b031690565b6001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a169083612817565b604080516001600160a01b0387168152602081018390527ff3136d14e8fd8ddb090effdbecb5d9547b6e17d9006ef9c25ca5b1e00dbfe51791015b60405180910390a15050505050565b6111d4610742565b156111fc57604051635185386160e11b815260016004820152600060248201526044016107d9565b600c546001600160a01b0316611225576040516306cf420760e31b815260040160405180910390fd5b600080600061123333612d01565b600c549295509093509150611260906001600160a01b0316826112568587614073565b6109019190614073565b600c546001600160a01b03166331f5be158261127c8587614073565b6112869190614073565b33888860405160200161129b93929190614120565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016112c7929190614143565b600060405180830381600087803b1580156112e157600080fd5b505af11580156112f5573d6000803e3d6000fd5b505050507f667838b33bdc898470de09e0e746990f2adc11b965b7fe6828e502ebc39e04343384848489896040516111bd9695949392919061415c565b61133a6128fd565b600854600160701b900463ffffffff16156113675760405162dc149f60e41b815260040160405180910390fd5b61139260017f0000000000000000000000000000000000000000000000000000000000000001612e72565b6113c76001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a16333085612b5c565b610a7360057f00000000000000000000000000000000000000000000000000000000000151808484612ef2565b6113fc6128fd565b6001600160a01b0381166114235760405163f6b2911f60e01b815260040160405180910390fd5b600980546001600160a01b0319166001600160a01b0383169081179091556040519081527f79f74fd5964b6943d8a1865abfb7f668c92fa3f32c0a2e3195da7d0946703ad790602001610bfb565b6001600160a01b03811660009081526001602052604081205461010090046001600160601b03168082036114a85750600092915050565b6001600160a01b03831660009081526001602052604090205460ff16156114d25750600092915050565b6001600160a01b0383166000908152600560205260409020546001600160601b031661153d611521837f0000000000000000000000000000000000000000000000000000000000000014612fcc565b6002546201000090046001600160601b03165b60059190612fe2565b610f989190614060565b61154f6128fd565b600854600160701b900463ffffffff16158015906115725750611570610742565b155b1561159a57604051635185386160e11b815260006004820152600160248201526044016107d9565b610a7360018383613008565b6115ae6120b1565b64e8d4a510008110156115db57604051631d820b1760e01b815264e8d4a5100060048201526024016107d9565b60006115ec64e8d4a510008361404c565b90508015611601576115fe8183614060565b91505b3360009081526001602052604090205460ff161561162857611623338361325c565b611632565b61163233836134e2565b610a736001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a16333085612b5c565b600254600090610768906201000090046001600160601b03167f00000000000000000000000000000000000000000000000000000000000000146137c8565b6116ae6120b1565b3360009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152929493860193879084015b8282101561174a576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611702565b50505090825250600291909101546001600160601b031660209091015280519091501561178a5760405163b7195bcb60e01b815260040160405180910390fd5b60006117c661152183602001516001600160601b03167f0000000000000000000000000000000000000000000000000000000000000014612fcc565b33600090815260056020526040812054919250906117ed906001600160601b031683614060565b90506117f882612083565b33600081815260056020526040902080546001600160601b0319166001600160601b03939093169290921790915561185b906001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a169083612817565b60408051338152602081018390527f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f724191016108a4565b6118996128fd565b6118a1610742565b156118c957604051635185386160e11b815260016004820152600060248201526044016107d9565b6002546118e4906201000090046001600160601b0316610c62565b611392610c75611667565b6004546000906118fd610980565b6040516370a0823160e01b81523060048201527f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a6001600160a01b0316906370a0823190602401602060405180830381865afa158015611961573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611985919061419d565b61198f9190614060565b6107689190614060565b6119a16120b1565b33600090815260016020819052604082200180549091036119d757604051630f1ed8ad60e21b81523360048201526024016107d9565b60008060005b8354811015611a9d5760008482815481106119fa576119fa614086565b9060005260206000209060020201905042858381548110611a1d57611a1d614086565b90600052602060002090600202016001015411611a84578054611a49906001600160601b031685614073565b8154600480549296506001600160601b0390911691600090611a6c908490614060565b90915550839050611a7c8161409c565b935050611a8a565b50611a9d565b5080611a958161409c565b9150506119dd565b508015611b995760005b8354611ab4908390614060565b811015611b405783611ac68383614073565b81548110611ad657611ad6614086565b9060005260206000209060020201848281548110611af657611af6614086565b60009182526020909120825460029092020180546001600160601b0319166001600160601b0390921691909117815560019182015491015580611b388161409c565b915050611aa7565b5060005b81811015611b975783805480611b5c57611b5c6141b6565b60008281526020812060026000199093019283020180546001600160601b031916815560010155905580611b8f8161409c565b915050611b44565b505b8115611bd357611bd36001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a163384612817565b60408051338152602081018490527fbb6ad7a7bb63a1746b89ec2f2339de4e0f983ef7e7b101c8c4f716c08c2a711491016108a4565b611c116128fd565b600a546001600160a01b0316611c3a576040516306cf420760e31b815260040160405180910390fd5b600b54611c4a9062093a80614073565b421015611c6a57604051631decfebb60e31b815260040160405180910390fd5b600a8054600c80546001600160a01b0383166001600160a01b031991821681179092559091169091556040519081527ffa33c052bbee754f3c0482a89962daffe749191fa33c696a61e947fbfd68bd84906020015b60405180910390a1565b611cd16128fd565b6001600160a01b038116611d365760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016107d9565b611d3f81612a19565b50565b606080600060016000016000856001600160a01b03166001600160a01b03168152602001908152602001600020600101805480602002602001604051908101604052809291908181526020016000905b82821015611dda576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611d92565b505050509050805167ffffffffffffffff811115611dfa57611dfa6141cc565b604051908082528060200260200182016040528015611e23578160200160208202803683370190505b509250805167ffffffffffffffff811115611e4057611e406141cc565b604051908082528060200260200182016040528015611e69578160200160208202803683370190505b50915060005b8151811015611f1557818181518110611e8a57611e8a614086565b602002602001015160000151848281518110611ea857611ea8614086565b60200260200101906001600160601b031690816001600160601b031681525050818181518110611eda57611eda614086565b602002602001015160200151838281518110611ef857611ef8614086565b602090810291909101015280611f0d8161409c565b915050611e6f565b5050915091565b60028301546001600160601b0316821015611f4d57604051630f9e1c3b60e11b8152600481018390526024016107d9565b6002830154600160601b90046001600160601b0316811015611f855760405163bc91aa3360e01b8152600481018290526024016107d9565b60028301546001600160601b03168214611ff857611fa282612083565b6002840180546001600160601b0319166001600160601b03929092169190911790556040518281527f7f4f497e086b2eb55f8a9885ba00d33399bbe0ebcb92ea092834386435a1b9c09060200160405180910390a15b6002830154600160601b90046001600160601b0316811461207e5761201c81612083565b6002840180546001600160601b0392909216600160601b026bffffffffffffffffffffffff60601b199092169190911790556040518181527fb5f554e5ef00806bace1edbb84186512ebcefa2af7706085143f501f29314df7906020016108a4565b505050565b60006001600160601b038211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b5090565b600054600160a01b900460ff1615610a555760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016107d9565b6001600160a01b03831660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152879687969095860193919290879084015b828210156121b6576000848152602090819020604080518082019091526002850290910180546001600160601b0316825260019081015482840152908352909201910161216e565b50505090825250600291909101546001600160601b03166020909101529050600085900361220257604051637ece672b60e11b81526001600160a01b03871660048201526024016107d9565b8481602001516001600160601b0316101561224157602081015160405163477d28dd60e01b81526001600160601b0390911660048201526024016107d9565b60025461225c906201000090046001600160601b0316610c62565b612267610c75611667565b805115612519577f0000000000000000000000000000000000000000000069e10de76676d080000085146122b957604051633ac3109f60e01b81526001600160a01b03871660048201526024016107d9565b60608101516001600160601b0316156122f057604051634ed4c4c760e11b81526001600160a01b03871660048201526024016107d9565b60006122fe87610f7c611667565b905061230986612083565b60028054600e9061232b908490600160701b90046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061235886612083565b6001600160a01b0388166000908152600160208190526040909120805490919061239190849061010090046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555084612491576123c386612083565b6001600160601b0316600160030160008282546123e09190614073565b90915550506001600160a01b038716600090815260016020819052604091829020825180840190935201908061241589612083565b6001600160601b0316815260200161244d7f000000000000000000000000000000000000000000000000000000000012750042614073565b90528154600180820184556000938452602093849020835160029093020180546001600160601b0319166001600160601b0390931692909217825591909201519101555b60068054600191906000906124aa90849060ff166141e2565b825461010092830a60ff818102199092169290911602179091556006546001600160a01b038a16600090815260056020526040812080546bffffffffffffffffffffffff60601b1916939092046001600160601b0316600160601b02929092179055945090925061280f915050565b6001600160a01b0386166000908152600560209081526040822054908301516001600160601b03918216916125739161152191167f0000000000000000000000000000000000000000000000000000000000000014612fcc565b61257d9190614060565b905061258886612083565b6002805481906125a89084906201000090046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506125d586612083565b6001600160a01b0388166000908152600160208190526040909120805490919061260e90849061010090046001600160601b03166140d7565b82546001600160601b0391821661010093840a90810290830219909116179092556001600160a01b038a16600090815260016020526040812054919091049091169003905061269257600780546001919060009061267390849063ffffffff166141fb565b92506101000a81548163ffffffff021916908363ffffffff1602179055505b8461276e576126a086612083565b6001600160601b0316600160030160008282546126bd9190614073565b90915550506001600160a01b03871660009081526001602081905260409182902082518084019093520190806126f289612083565b6001600160601b0316815260200161272a7f000000000000000000000000000000000000000000000000000000000012750042614073565b90528154600180820184556000938452602093849020835160029093020180546001600160601b0319166001600160601b0390931692909217825591909201519101555b6001600160a01b0387166000908152600160205260409020546127cc906127c7906115219061010090046001600160601b03167f0000000000000000000000000000000000000000000000000000000000000014612fcc565b612083565b6001600160a01b038816600090815260056020526040812080546001600160601b0319166001600160601b039390931692909217909155909350915061280f9050565b935093915050565b6040516001600160a01b03831660248201526044810182905261207e90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526137d4565b60408051608081018252600183015460ff8082161515835261010082041660208301526001600160601b036201000082048116938301849052600160701b9091041660608201819052600092610f989190614073565b6000546001600160a01b03163314610a555760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016107d9565b61295f6138a6565b6000805460ff60a01b191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b039091168152602001611cbf565b6129af610742565b610a5557604051635185386160e11b815260006004820152600160248201526044016107d9565b6129de6120b1565b6000805460ff60a01b1916600160a01b1790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25861298f3390565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b612a766127c783836138ff565b6002830180546001600160601b0392909216640100000000026fffffffffffffffffffffffff0000000019909216919091179055612abb612ab6836139ff565b613a1e565b6002909201805463ffffffff93909316600160801b0263ffffffff60801b199093169290921790915550565b612af56127c7848484613a45565b6001840180546001600160601b0392909216610100026cffffffffffffffffffffffff0019909216919091179055612b2f612ab6846139ff565b6001909301805463ffffffff94909416600160681b0263ffffffff60681b19909416939093179092555050565b6040516001600160a01b0380851660248301528316604482015260648101829052612b949085906323b872dd60e01b90608401612843565b50505050565b6003840154600090849042600160501b90910463ffffffff161115612bf6576003860154612bd6904290600160501b900463ffffffff16614060565b6003870154612bf1919069ffffffffffffffffffff16614218565b612bf9565b60005b612c039190614073565b905081831015612c255760405162da056d60e81b815260040160405180910390fd5b612c32612ab68442614073565b60038601805463ffffffff92909216600160501b026dffffffff0000000000000000000019909216919091179055612c72612c6d848361422f565b613b50565b600395909501805469ffffffffffffffffffff191669ffffffffffffffffffff9096169590951790945550505050565b6001600160a01b038316600090815260208590526040812054600160601b90046001600160601b0316612cd6868585613b7d565b612ce09190614060565b90505b949350505050565b6000818310612cfa5781610f98565b5090919050565b6001600160a01b03811660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b031681850152938101805483518186028101860185528181528796879687969195949186019390879084015b82821015612dae576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101612d66565b50505090825250600291909101546001600160601b039081166020928301529082015191925016600003612e0057604051637256ef3960e11b81526001600160a01b03861660048201526024016107d9565b60608101516001600160601b031615612e3757604051634ed4c4c760e11b81526001600160a01b03861660048201526024016107d9565b600080612e538784602001516001600160601b0316600161210b565b6020909401516001600160601b03169650945091925050509193909250565b6001820154610100900460ff16811115612eb557600182015460405163e709379960e01b815261010090910460ff166004820152602481018290526044016107d9565b6001828101805460ff191690911790556040517fded6ebf04e261e1eb2f3e3b268a2e6aee5b478c15b341eba5cf18b9bc80c2e6390600090a15050565b6000612efd42613a1e565b60038601805471ffffffff00000000000000000000000000001916600160701b63ffffffff84169081029190911790915560018701805463ffffffff60681b1916600160681b830217905560028701805463ffffffff60801b1916600160801b9092029190911790559050612f7485848487612b9a565b60038501546040805185815263ffffffff600160701b840481166020830152600160501b909304909216908201527f4398f7e311b2f8164ce7d424166e6e84fd9a74adda069e902beead8e4eb737b8906060016111bd565b6000612fd883836137c8565b610f989084614060565b600064e8d4a51000612ff485846138ff565b612ffe9085614218565b612ce3919061422f565b60005b818110156132185783600084848481811061302857613028614086565b905060200201602081019061303d9190613e63565b6001600160a01b0316815260208101919091526040016000205460ff16156130aa5782828281811061307157613071614086565b90506020020160208101906130869190613e63565b604051625290b360e11b81526001600160a01b0390911660048201526024016107d9565b600084818585858181106130c0576130c0614086565b90506020020160208101906130d59190613e63565b6001600160a01b0316815260208101919091526040016000205461010090046001600160601b0316111561314f5782828281811061311557613115614086565b905060200201602081019061312a9190613e63565b60405163602d4d1160e01b81526001600160a01b0390911660048201526024016107d9565b600184600085858581811061316657613166614086565b905060200201602081019061317b9190613e63565b6001600160a01b031681526020810191909152604001600020805460ff19169115159190911790557fac6fa858e9350a46cec16539926e0fde25b7629f84b5a72bffaae4df888ae86d8383838181106131d6576131d6614086565b90506020020160208101906131eb9190613e63565b6040516001600160a01b03909116815260200160405180910390a1806132108161409c565b91505061300b565b5061322281613b8a565b60018401546132399190610100900460ff16614243565b6001909301805460ff949094166101000261ff0019909416939093179092555050565b6001600160a01b0382166000908152600160205260408120805490916101009091046001600160601b0316906132928483614073565b90507f0000000000000000000000000000000000000000000069e10de76676d08000008110156132f757604051631d820b1760e01b81527f0000000000000000000000000000000000000000000069e10de76676d080000060048201526024016107d9565b7f0000000000000000000000000000000000000000000069e10de76676d0800000811115613365576133497f0000000000000000000000000000000000000000000069e10de76676d080000082614060565b604051631728673b60e31b81526004016107d991815260200190565b8160000361340857613378610c75611667565b60065460ff16600081900361339e57600680546cffffffffffffffffffffffff00191690555b6133a9816001614243565b6006805460ff191660ff9290921691909117908190556001600160a01b038716600090815260056020526040902080546bffffffffffffffffffffffff60601b19166101009092046001600160601b0316600160601b02919091179055505b61341184612083565b60028054600e90613433908490600160701b90046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061346081612083565b6001600160a01b03861660008181526001602090815260409182902080546001600160601b0395909516610100026cffffffffffffffffffffffff00199095169490941790935580519182529181018690529081018290527f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee90906060016111bd565b6134ea6129a7565b6001600160a01b03821660009081526001602052604081205461010090046001600160601b03169061351c8383614073565b90507f000000000000000000000000000000000000000000000000000000e8d4a5100081101561358157604051631d820b1760e01b81527f000000000000000000000000000000000000000000000000000000e8d4a5100060048201526024016107d9565b600354600160601b90046001600160601b0316808211156135a6576133498382614060565b60006135b26001613bae565b9050808511156135d857604051631728673b60e31b8152600481018290526024016107d9565b6002546135f3906201000090046001600160601b0316610c62565b6135fe610c75611667565b8360000361364157600780546001919060009061362290849063ffffffff1661425c565b92506101000a81548163ffffffff021916908363ffffffff1602179055505b600061366d867f0000000000000000000000000000000000000000000000000000000000000014612fcc565b905061368f6127c7826115346002546001600160601b03620100009091041690565b6001600160a01b038816600090815260056020526040812080549091906136c09084906001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506136ed86612083565b60028054819061370d9084906201000090046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061373a84612083565b6001600160a01b03881660008181526001602090815260409182902080546001600160601b0395909516610100026cffffffffffffffffffffffff00199095169490941790935580519182529181018890529081018590527f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee909060600160405180910390a150505050505050565b6000610f98828461422f565b6000613829826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316613c0a9092919063ffffffff16565b80519091501561207e578080602001905181019061384791906140b5565b61207e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016107d9565b600054600160a01b900460ff16610a555760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f742070617573656400000000000000000000000060448201526064016107d9565b6000816000036139255750600282015464010000000090046001600160601b0316610c23565b600383015460009042600160501b90910463ffffffff16111561396357600284015461395e90600160801b900463ffffffff1642614060565b61398e565b6002840154600385015461398e9163ffffffff600160801b909104811691600160501b900416614060565b60038501549091506139ce90849064e8d4a51000906139ba90859069ffffffffffffffffffff16614218565b6139c49190614218565b6127c7919061422f565b60028501546139ee919064010000000090046001600160601b031661400f565b6001600160601b0316949350505050565b6003810154600090610c2390600160501b900463ffffffff1642612ceb565b600063ffffffff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b600081600003613a685750600183015461010090046001600160601b0316610f98565b600384015460009042600160501b90910463ffffffff161115613aa6576001850154613aa190600160681b900463ffffffff1642614060565b613ad1565b60018501546003860154613ad19163ffffffff600160681b909104811691600160501b900416614060565b600180870154919250613b2191613aed9160ff90911690613c19565b600387015485908790613b0d90869069ffffffffffffffffffff16614218565b613b179190614218565b6139c4919061422f565b6001860154613b3e919061010090046001600160601b031661400f565b6001600160601b031695945050505050565b600069ffffffffffffffffffff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b6000612ce3848484613a45565b600060ff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b60408051608081018252600183015460ff8082161515835261010082041660208301526001600160601b036201000082048116938301849052600160701b909104811660608301526002840154600093610f9892909116614060565b6060612ce38484600085613c28565b6000818311612cfa5781610f98565b606082471015613c895760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016107d9565b600080866001600160a01b03168587604051613ca59190614279565b60006040518083038185875af1925050503d8060008114613ce2576040519150601f19603f3d011682016040523d82523d6000602084013e613ce7565b606091505b5091509150613cf887838387613d03565b979650505050505050565b60608315613d72578251600003613d6b576001600160a01b0385163b613d6b5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107d9565b5081612ce3565b612ce38383815115613d875781518083602001fd5b8060405162461bcd60e51b81526004016107d99190613df1565b60005b83811015613dbc578181015183820152602001613da4565b50506000910152565b60008151808452613ddd816020860160208601613da1565b601f01601f19169290920160200192915050565b602081526000610f986020830184613dc5565b80356001600160a01b0381168114613e1b57600080fd5b919050565b60008060408385031215613e3357600080fd5b613e3c83613e04565b946020939093013593505050565b600060208284031215613e5c57600080fd5b5035919050565b600060208284031215613e7557600080fd5b610f9882613e04565b60008060408385031215613e9157600080fd5b50508035926020909101359150565b60008060208385031215613eb357600080fd5b823567ffffffffffffffff80821115613ecb57600080fd5b818501915085601f830112613edf57600080fd5b813581811115613eee57600080fd5b866020828501011115613f0057600080fd5b60209290920196919550909350505050565b60008060208385031215613f2557600080fd5b823567ffffffffffffffff80821115613f3d57600080fd5b818501915085601f830112613f5157600080fd5b813581811115613f6057600080fd5b8660208260051b8501011115613f0057600080fd5b604080825283519082018190526000906020906060840190828701845b82811015613fb75781516001600160601b031684529284019290840190600101613f92565b5050508381038285015284518082528583019183019060005b81811015613fec57835183529284019291840191600101613fd0565b5090979650505050505050565b634e487b7160e01b600052601160045260246000fd5b6001600160601b0381811683821601908082111561402f5761402f613ff9565b5092915050565b634e487b7160e01b600052601260045260246000fd5b60008261405b5761405b614036565b500690565b81810381811115610c2357610c23613ff9565b80820180821115610c2357610c23613ff9565b634e487b7160e01b600052603260045260246000fd5b6000600182016140ae576140ae613ff9565b5060010190565b6000602082840312156140c757600080fd5b81518015158114610f9857600080fd5b6001600160601b0382811682821603908082111561402f5761402f613ff9565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0384168152604060208201526000612ce06040830184866140f7565b828152604060208201526000612ce36040830184613dc5565b6001600160a01b038716815285602082015284604082015283606082015260a06080820152600061419160a0830184866140f7565b98975050505050505050565b6000602082840312156141af57600080fd5b5051919050565b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b60ff8281168282160390811115610c2357610c23613ff9565b63ffffffff82811682821603908082111561402f5761402f613ff9565b8082028115828204841417610c2357610c23613ff9565b60008261423e5761423e614036565b500490565b60ff8181168382160190811115610c2357610c23613ff9565b63ffffffff81811683821601908082111561402f5761402f613ff9565b6000825161428b818460208701613da1565b919091019291505056fea26469706673582212204b33beeb15f45e326d7db3f299b4a8074df7350d41370ad4fcb664fb96c7ff3564736f6c63430008120033

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

000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a000000000000000000000000000000000000000000295be96e640669720000000000000000000000000000000000000000000000000211654585005212800000000000000000000000000000000000000000000000000000000000e8d4a510000000000000000000000000000000000000000000000069e10de76676d08000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000001518000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000127500

-----Decoded View---------------
Arg [0] : params (tuple): System.Collections.Generic.List`1[Nethereum.ABI.FunctionEncoding.ParameterOutput]

-----Encoded View---------------
9 Constructor Arguments found :
Arg [0] : 000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a
Arg [1] : 000000000000000000000000000000000000000000295be96e64066972000000
Arg [2] : 0000000000000000000000000000000000000000000211654585005212800000
Arg [3] : 000000000000000000000000000000000000000000000000000000e8d4a51000
Arg [4] : 0000000000000000000000000000000000000000000069e10de76676d0800000
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [6] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [7] : 0000000000000000000000000000000000000000000000000000000000000014
Arg [8] : 0000000000000000000000000000000000000000000000000000000000127500


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

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