Contract 0x9cF5E53D4B42A18bdea7A9BA7af861facC9D48f2

 
Txn Hash Method
Block
From
To
Value [Txn Fee]
0x4cab160412e20e850b507eb8213dadfd7923a94d22ff70d33d670b6629b348dc0x60806040237298882022-01-15 1:22:163 days 21 hrs ago0x9f60699ce23f1ab86ec3e095b477ff79d4f409ad IN  Create: UserModule0 MATIC0.5337584100
[ Download CSV Export 
Parent Txn Hash Block From To Value
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
UserModule

Compiler Version
v0.8.6+commit.11564f7e

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 19 : main.sol
pragma solidity ^0.8.0;

import "./helpers.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract CoreInternals is Helpers {
    using SafeMath for uint;
    using SafeERC20 for IERC20;

    struct OverallPosition {
        address pool_;
        address token0_;
        address token1_;
        address poolAddr_;
        uint128 liquidity_;
        uint totalSupplyInUsd_;
        uint totalNormalSupplyInUsd_;
        uint totalBorrowInUsd_;
        uint totalNormalBorrowInUsd_;
    }

    /**
    * @dev deposit's NFT and creates a debt position against it.
    * @param owner_ owner of the NFT.
    * @param NFTID_ ID of NFT.
    */
    function deposit(address owner_, uint256 NFTID_) internal {
        _position[owner_][NFTID_] = true;
        (
            ,
            ,
            address token0_,
            address token1_,
            uint24 fee_,
            int24 tickLower_,
            int24 tickUpper_,
            ,
            ,
            ,
            ,
        ) = nftManager.positions(NFTID_);
        address pool_ = getPoolAddress(token0_, token1_, fee_);
        require(poolEnabled_[pool_], "NFT-pool-not-enabled");
        require(uint24(tickUpper_ - tickLower_) > _minTick[pool_], "less-ticks-difference");

        emit depositLog(owner_, NFTID_);
    }

    /**
    * @dev remove liquidity from an NFT. Called when owner withdraws or liquidator liquidates.
    * @param NFTID_ NFT ID
    * @param liquidity_ liquidity to withdraw
    * @param amount0Min_ minimum amount0 to withdraw
    * @param amount1Min_ minimum amount1 to withdraw
    * @param isLiquidate_ position got liquidated or just a normal withdrawal
    */
    function _removeLiquidity(
        uint96 NFTID_,
        uint256 liquidity_,
        uint256 amount0Min_,
        uint256 amount1Min_,
        bool isLiquidate_
    )
        internal
        returns (uint256 exactAmount0_, uint256 exactAmount1_)
    {
        (exactAmount0_, exactAmount1_) = _decreaseLiquidity(
            NFTID_,
            liquidity_,
            0,
            0
        );
        require(exactAmount0_ > amount0Min_, "less-than-min-amount");
        require(exactAmount1_ > amount1Min_, "less-than-min-amount");
            address token0_;
        address token1_;
        if (isLiquidate_) {
            (,, token0_, token1_,,,,,,,,) = nftManager.positions(NFTID_);
        } else {
            address pool_;
            uint totalSupplyInUsd_;
            uint totalNormalSupplyInUsd_;
            uint totalBorrowInUsd_;
            uint totalNormalBorrowInUsd_;
            (
                pool_,
                token0_,
                token1_,
                ,
                totalSupplyInUsd_,
                totalNormalSupplyInUsd_,
                totalBorrowInUsd_,
                totalNormalBorrowInUsd_
            ) = getOverallPosition(
                NFTID_
            );
            bool isOk_ = liquidationCheck(
                pool_,
                totalSupplyInUsd_,
                totalBorrowInUsd_,
                totalNormalSupplyInUsd_,
                totalNormalBorrowInUsd_
            );
            require(isOk_, "position-will-liquidate");
        }
        _collect(NFTID_, type(uint128).max, type(uint128).max);
    }

    /**
    * @dev collect fee accrued of an NFT. Throws if on withdrawing fees position is going to Liquidate.
    * @param NFTID_ NFT ID.
    * @return amount0_ amount0 collected.
    * @return amount1_ amount1 collected.
    */
    function _collectFees(uint96 NFTID_, bool isLiquidate_)
        internal
        returns (uint256 amount0_, uint256 amount1_)
    {
        (amount0_, amount1_) = _collect(NFTID_, type(uint128).max, type(uint128).max);
        address token0_;
        address token1_;
        if (isLiquidate_) {
            (,, token0_, token1_,,,,,,,,) = nftManager.positions(NFTID_);
        } else {
            address pool_;
            uint totalSupplyInUsd_;
            uint totalNormalSupplyInUsd_;
            uint totalBorrowInUsd_;
            uint totalNormalBorrowInUsd_;
            (
                pool_,
                token0_,
                token1_,
                ,
                totalSupplyInUsd_,
                totalNormalSupplyInUsd_,
                totalBorrowInUsd_,
                totalNormalBorrowInUsd_
            ) = getOverallPosition(
                NFTID_
            );
            bool isOk_ = liquidationCheck(
                pool_,
                totalSupplyInUsd_,
                totalBorrowInUsd_,
                totalNormalSupplyInUsd_,
                totalNormalBorrowInUsd_
            );
            require(isOk_, "position-will-liquidate");
        }
        IERC20 token0 = IERC20(token0_);
        IERC20 token1 = IERC20(token1_);
        token0.safeTransfer(msg.sender, amount0_);
        token1.safeTransfer(msg.sender, amount1_);
    }

    /**
    * @dev Liquidate an NFT. Only applicable when position crosses borrow limit. Owner loses entire collateral.
    * @param NFTID_ NFT ID
    * @param amount0Min_ amount0 min acceptable amount on withdraw
    * @return exactAmount0_ exact amount 0 retuned.
    * @return exactAmount1_ exact amount 1 retuned.
    * @return markets_ array of borrow tokens for the NFT pool.
    * @return paybackAmts_ array of payed back amounts.
    */
    function _liquidate(
        uint96 NFTID_,
        uint amount0Min_,
        uint amount1Min_
    ) internal returns (
        uint exactAmount0_,
        uint exactAmount1_,
        address[] memory markets_,
        uint[] memory paybackAmts_
    ) {
        OverallPosition memory overallPosition_;
        (
            overallPosition_.pool_,
            ,
            ,
            overallPosition_.liquidity_,
            overallPosition_.totalSupplyInUsd_,
            overallPosition_.totalNormalSupplyInUsd_,
            overallPosition_.totalBorrowInUsd_,
            overallPosition_.totalNormalBorrowInUsd_
        ) = getOverallPosition(
            NFTID_
        );
        verifyOracles(overallPosition_.pool_);
        bool isOk_ = liquidationCheck(
            overallPosition_.pool_,
            overallPosition_.totalSupplyInUsd_,
            overallPosition_.totalBorrowInUsd_,
            overallPosition_.totalNormalSupplyInUsd_,
            overallPosition_.totalNormalBorrowInUsd_
        );
        require(!isOk_, "position-will-liquidate");
        for (uint i = 0; i < markets_.length; i++) {
            markets_ = _poolMarkets[overallPosition_.pool_];
            paybackAmts_ = new uint[](markets_.length);

            (paybackAmts_[i]) = _payback(
                NFTID_,
                markets_[i],
                type(uint).max
            );
            if (paybackAmts_[i] > 0) {
                IERC20 token_ = IERC20(markets_[i]);
                token_.safeTransferFrom(msg.sender, address(this), paybackAmts_[i]);
                token_.approve(address(liquidity), paybackAmts_[i]);
                liquidity.payback(markets_[i], paybackAmts_[i]);
            }
        }
        (exactAmount0_, exactAmount1_) = _removeLiquidity(
            NFTID_,
            overallPosition_.liquidity_, // in percent, 1e18 = 100% withdrawal
            amount0Min_,
            amount1Min_,
            true
        );
        (uint amount0Fee_, uint amount1Fee_) = _collectFees(NFTID_, true);

        emit liquidateLog(
            NFTID_,
            (exactAmount0_ + amount0Fee_),
            (exactAmount1_ + amount1Fee_),
            markets_,
            paybackAmts_
        );
    }

    function _stake(
        address rewardToken_,
        uint256 startTime_,
        uint256 endTime_,
        address refundee_,
        uint256 tokenId_
    ) internal {
        (
            address token0_,
            address token1_,
            uint24 fee_
        ) = getNftTokenPairAddresses(tokenId_);

        address poolAddr_ = getPoolAddress(token0_, token1_, fee_);

        IUniswapV3Pool pool = IUniswapV3Pool(poolAddr_);
        IUniswapV3Staker.IncentiveKey memory _key = IUniswapV3Staker
            .IncentiveKey(
                IERC20Minimal(rewardToken_),
                pool,
                startTime_,
                endTime_,
                refundee_
            );
        
        staker.stakeToken(_key, tokenId_);
    }

    struct TokenPair {
        address token0;
        address token1;
        uint24 fee;
    }

    function _unstake(
        address rewardToken_,
        uint256 startTime_,
        uint256 endTime_,
        address refundee_,
        uint96 NFTID_
    ) internal {
        TokenPair memory tp_;
        (
            tp_.token0,
            tp_.token1,
            tp_.fee
        ) = getNftTokenPairAddresses(NFTID_);
        
        address poolAddr_ = getPoolAddress(tp_.token0, tp_.token1, tp_.fee);

        IERC20Minimal rewardTokenContract_ = IERC20Minimal(rewardToken_);

        IUniswapV3Pool pool_ = IUniswapV3Pool(poolAddr_);
        IUniswapV3Staker.IncentiveKey memory key_ = IUniswapV3Staker
            .IncentiveKey(
                rewardTokenContract_,
                pool_,
                startTime_,
                endTime_,
                refundee_
            );

        uint initialReward_ = staker.rewards(rewardTokenContract_, address(this));

        staker.unstakeToken(key_, NFTID_);

        uint finalReward_ = staker.rewards(rewardTokenContract_, address(this));
        _rewardAccrued[NFTID_][rewardToken_] += (finalReward_ - initialReward_);
    }

    function _claimRewards(
        IERC20Minimal rewardToken_,
        address to_,
        uint256 amountRequested_
    ) internal {
        staker.claimReward(rewardToken_, to_, amountRequested_);
    }

}

contract UserModule is CoreInternals {
    using SafeMath for uint;
    using SafeERC20 for IERC20;

    modifier nonReentrant() {
        require(_status != 2, "ReentrancyGuard: reentrant call");
        _status = 2;
        _;
        _status = 1;
    }

    /**
    * @dev modifier the verify owner of NFT position. msg.sender should be an owner.
    * @param NFTID_ ID of NFT.
    */
    modifier isPositionOwner(uint96 NFTID_) {
        require(_position[msg.sender][NFTID_], "not-an-owner");
        _;
    }

    /**
    * @dev Triggers when an ERC721 token receives to this contract.
    * @param _operator Person who initiated the transfer of NFT.
    * @param _from owner of NFT.
    * @param _id ID of NFT.
    */
    function onERC721Received(
        address _operator,
        address _from,
        uint256 _id,
        bytes calldata
    ) external nonReentrant returns (bytes4) {
        require(_operator == _from, "operator-should-be-the-owner");
        require(msg.sender == nftManagerAddr, "Not-Uniswap-V3-NFT");
        deposit(_from, _id);
        return 0x150b7a02;
    }

    /**
    * @dev Withdraws the NFT from contract. NFT should have 0 debt.
    * @param NFTID_ NFT ID
    */
    function withdraw(uint96 NFTID_) external isPositionOwner(NFTID_) nonReentrant {
        bool has_ = hasDebt(NFTID_);
        require(!has_, "debt-should-be-0");
        _position[msg.sender][NFTID_] = false;
        nftManager.safeTransferFrom(address(this), msg.sender, NFTID_);

        emit withdrawLog(NFTID_);
    }

    /**
    * @dev add more liquidity to NFT. Open function - anyone can call.
    * @param NFTID_ NFT ID
    * @param amount0_ desired amount0 to deposit
    * @param amount1_ desired amount1 to withdraw
    * @param minAmount0_ minimum amount0 to deposit
    * @param minAmount1_ minimum amount1 to deposit
    * @param deadline_ deadline of transaction
    * @return exactAmount0_ exact amount0 deposited
    * @return exactAmount1_ exact amount1 deposited
    */
    function addLiquidity(
        uint96 NFTID_,
        uint256 amount0_,
        uint256 amount1_,
        uint256 minAmount0_,
        uint256 minAmount1_,
        uint256 deadline_
    ) external nonReentrant  returns (uint256 exactAmount0_, uint256 exactAmount1_) {
        (address token0Addr_, address token1Addr_, ) = getNftTokenPairAddresses(NFTID_);
        IERC20 token0_ = IERC20(token0Addr_);
        IERC20 token1_ = IERC20(token1Addr_);
        token0_.safeTransferFrom(msg.sender, address(this), amount0_);
        token1_.safeTransferFrom(msg.sender, address(this), amount1_);
        token0_.safeApprove(nftManagerAddr, amount0_);
        token1_.safeApprove(nftManagerAddr, amount1_);
        (, exactAmount0_, exactAmount1_) = _addLiquidity(
            NFTID_,
            amount0_,
            amount1_,
            minAmount0_,
            minAmount1_,
            deadline_
        );
        require(exactAmount0_ > minAmount0_, "less-than-min-amount");
        require(exactAmount1_ > minAmount1_, "less-than-min-amount");
        token0_.safeTransfer(msg.sender, amount0_ - exactAmount0_);
        token1_.safeTransfer(msg.sender, amount1_ - exactAmount1_);

        emit addLiquidityLog(
            NFTID_,
            exactAmount0_,
            exactAmount1_,
            deadline_
        );
    }

    /**
    * @dev removes liquidity from an NFT.
    * @param NFTID_ NFT ID
    * @param liquidity_ liquidity to withdraw
    * @param amount0Min_ minimum amount0 to withdraw
    * @param amount1Min_ minimum amount1 to withdraw
    * @return exactAmount0_ exact amount0 withdrawn
    * @return exactAmount1_ exact amount1 withdrawn
    */
    function removeLiquidity(
        uint96 NFTID_,
        uint256 liquidity_,
        uint256 amount0Min_,
        uint256 amount1Min_
    )
        external
        nonReentrant
        isPositionOwner(NFTID_)
        returns (uint256 exactAmount0_, uint256 exactAmount1_)
    {
        (exactAmount0_, exactAmount1_) = _removeLiquidity(
            NFTID_,
            liquidity_,
            amount0Min_,
            amount1Min_,
            false
        );

        emit removeLiquidityLog(NFTID_, exactAmount0_, exactAmount1_);
    }

    /**
    * @dev borrow against an NFT debt position. Throws if borrow more than borrow limit.
    * @param NFTID_ NFT ID
    * @param token_ token to borrow
    * @param amount_ amount to borrow
    */
    function borrow(
        uint96 NFTID_,
        address token_,
        uint256 amount_
    ) external isPositionOwner(NFTID_) nonReentrant {
        _borrow(NFTID_, token_, amount_);
        (
            address pool_,
            ,
            ,
            ,
            uint totalSupplyInUsd_,
            uint totalNormalSupplyInUsd_,
            uint totalBorrowInUsd_,
            uint totalNormalBorrowInUsd_
        ) = getOverallPosition(
            NFTID_
        );
        require(_borrowAllowed[pool_][token_], "token-not-allowed-for-borrow");
        bool isOk_ = liquidationCheck(
            pool_,
            totalSupplyInUsd_,
            totalBorrowInUsd_,
            totalNormalSupplyInUsd_,
            totalNormalBorrowInUsd_
        );
        require(isOk_, "position-will-liquidate");
        liquidity.borrow(token_, amount_);

        IERC20(token_).safeTransfer(msg.sender, amount_);

        emit borrowLog(NFTID_, token_, amount_);
    }

    /**
    * @dev payback debt of an NFT debt position.
    * @param NFTID_ NFT ID.
    * @param token_ token to payback.
    * @param amount_ amount to payback.
    * @return exactAmount_ exact amount payed back.
    */
    function payback(
        uint96 NFTID_,
        address token_,
        uint256 amount_
    ) external nonReentrant returns (uint exactAmount_) {
        IERC20 tokenContract_ = IERC20(token_);
        exactAmount_ = _payback(
            NFTID_,
            token_,
            amount_
        );
        if (exactAmount_ > 0) {
            tokenContract_.safeTransferFrom(msg.sender, address(this), exactAmount_);
            tokenContract_.safeApprove(address(liquidity), exactAmount_);
            liquidity.payback(token_, exactAmount_);
        }

        emit paybackLog(NFTID_, token_, amount_);
    }

    /**
    * @dev collect fee accrued of an NFT. Throws if on withdrawing fees position is going to Liquidate.
    * @param NFTID_ NFT ID.
    * @return amount0_ amount0 collected.
    * @return amount1_ amount1 collected.
    */
    function collectFees(uint96 NFTID_)
        external
        isPositionOwner(NFTID_)
        nonReentrant
        returns (uint256 amount0_, uint256 amount1_)
    {
        (amount0_, amount1_) = _collectFees(NFTID_, false);

        emit collectFeeLog(NFTID_, amount0_, amount1_);
    }

    /**
    * @dev Liquidate an NFT. Only applicable when position crosses borrow limit. Owner loses entire collateral.
    * @param NFTID_ NFT ID
    * @param amount0Min_ amount0 min acceptable amount on withdraw
    * @param amount1Min_ amount1 min acceptable amount on withdraw
    * @return exactAmount0_ exact amount 0 retuned.
    * @return exactAmount1_ exact amount 1 retuned.
    * @return markets_ array of borrow tokens for the NFT pool.
    * @return paybackAmts_ array of payed back amounts.
    */
    function liquidate(
        uint96 NFTID_,
        uint amount0Min_,
        uint amount1Min_
    ) external nonReentrant returns (
        uint exactAmount0_,
        uint exactAmount1_,
        address[] memory markets_,
        uint[] memory paybackAmts_
    ) {
        (
            exactAmount0_,
            exactAmount1_,
            markets_,
            paybackAmts_
        ) = _liquidate(
            NFTID_,
            amount0Min_,
            amount1Min_
        );
    }

    struct LiquidateVariables {
        uint exactAmount0;
        uint exactAmount1;
        address[] markets;
        uint[] paybackAmts;
    }

    /**
    * @dev Liquidate an NFT. Only applicable when position crosses borrow limit. Owner loses entire collateral.
    * @param NFTID_ NFT ID
    * @param amount0Min_ amount0 min acceptable amount on withdraw
    * @param amount1Min_ amount1 min acceptable amount on withdraw
    * @param rewardTokens_ reward tokens for which to unstake
    * @param startTime_ start time of rewards for which to unstake
    * @param endTime_ end time of rewards for which to unstake
    * @param refundee_ refundee of rewards for which to unstake
    */
    function liquidate(
        uint96 NFTID_,
        uint amount0Min_,
        uint amount1Min_,
        address[] memory rewardTokens_,
        uint256[] memory startTime_,
        uint256[] memory endTime_,
        address[] memory refundee_
    ) external nonReentrant returns (LiquidateVariables memory v_) {
        require(_isStaked[NFTID_], "NFT-not-staked");
        for (uint i = 0; i < rewardTokens_.length; i++) {
            _unstake(rewardTokens_[i], startTime_[i], endTime_[i], refundee_[i], NFTID_);
        }
        staker.withdrawToken(NFTID_, address(this), "");
        _isStaked[NFTID_] = false;
        (
            v_.exactAmount0,
            v_.exactAmount1,
            v_.markets,
            v_.paybackAmts
        ) = _liquidate(
            NFTID_,
            amount0Min_,
            amount1Min_
        );
        return v_;
    }

    /**
     * @dev Deposit NFT token
     * @notice Transfer deposited NFT token
     * @param NFTID_ NFT LP Token ID
     */
    function depositNFT(uint96 NFTID_)
        external
        isPositionOwner(NFTID_) nonReentrant
    {
        _isStaked[NFTID_] = true;
        nftManager.safeTransferFrom(
            address(this),
            address(staker),
            NFTID_
        );
    }

    /**
     * @dev Withdraw NFT LP token
     * @notice Withdraw NFT LP token from staking pool
     * @param NFTID_ NFT LP Token ID
     */
    function withdrawNFT(uint96 NFTID_)
        external
        isPositionOwner(NFTID_) nonReentrant
    {
        staker.withdrawToken(NFTID_, address(this), "");
        _isStaked[NFTID_] = false;
    }

    /**
     * @dev Stake NFT LP token
     * @notice Stake NFT LP Position
     * @param rewardToken_ _rewardToken address
     * @param startTime_ stake start time
     * @param endTime_ stake end time
     * @param refundee_ refundee address
     * @param NFTID_ NFT LP token id
     */
    function stake(
        address rewardToken_,
        uint256 startTime_,
        uint256 endTime_,
        address refundee_,
        uint96 NFTID_
    )
        external
        isPositionOwner(NFTID_) nonReentrant
    {
        _stake(rewardToken_, startTime_, endTime_, refundee_, NFTID_);
    }

    /**
     * @dev Unstake NFT LP token
     * @notice Unstake NFT LP Position
     * @param rewardToken_ _rewardToken address
     * @param startTime_ stake start time
     * @param endTime_ stake end time
     * @param refundee_ refundee address
     * @param NFTID_ NFT LP token id
     */
    function unstake(
        address rewardToken_,
        uint256 startTime_,
        uint256 endTime_,
        address refundee_,
        uint96 NFTID_
    )
        external
        isPositionOwner(NFTID_) nonReentrant
    {
        _unstake(rewardToken_, startTime_, endTime_, refundee_, NFTID_);
    }

    /**
     * @dev Claim rewards
     * @notice Claim rewards
     * @param rewardToken_ _rewardToken address
     * @param NFTID_ NFT ID
     */
    function claimRewards(
        address rewardToken_,
        uint96 NFTID_
    )
        external
        isPositionOwner(NFTID_) nonReentrant
        returns (uint256 rewards_)
    {
        rewards_ = _rewardAccrued[NFTID_][rewardToken_];
        _rewardAccrued[NFTID_][rewardToken_] = 0;
        _claimRewards(
            IERC20Minimal(rewardToken_),
            msg.sender,
            rewards_
        );
    }

}

File 2 of 19 : helpers.sol
pragma solidity ^0.8.0;

import "../common/variables.sol";
import "./events.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./libraries/TickMath.sol";
import "./libraries/FullMath.sol";
import "./libraries/FixedPoint128.sol";
import "./libraries/LiquidityAmounts.sol";
import "./libraries/PositionKey.sol";
import "./interfaces.sol";


contract UniswapHelpers is Variables, Events {
    using SafeMath for uint256;

    bytes32 internal constant POOL_INIT_CODE_HASH =
        0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
    address internal constant nftManagerAddr =
        0xC36442b4a4522E871399CD717aBDD847Ab11FE88;
    INonfungiblePositionManager internal constant nftManager =
        INonfungiblePositionManager(nftManagerAddr);
    IUniswapV3Staker internal constant staker =
        IUniswapV3Staker(0xe34139463bA50bD61336E0c446Bd8C0867c6fE65);
    address internal constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address internal constant WBTC_ADDRESS = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
    address internal constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    struct PoolKey {
        address token0;
        address token1;
        uint24 fee;
    }

    struct Position {
        uint24 fee_;
        int24 tickLower_;
        int24 tickUpper_;
        uint256 feeGrowthInside0LastX128_;
        uint256 feeGrowthInside1LastX128_;
        uint128 tokensOwed0_;
        uint128 tokensOwed1_;
        uint256 amount0_;
        uint256 amount1_;
        uint256 feeAccrued0_;
        uint256 feeAccrued1_;
    }

    /**
     * @dev computes the address of pool
     * @param factory_ factory address
     * @param key_ PoolKey struct
     */
    function computeAddress(address factory_, PoolKey memory key_)
        internal
        pure
        returns (address pool_)
    {
        require(key_.token0 < key_.token1);
        pool_ = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory_,
                            keccak256(
                                abi.encode(key_.token0, key_.token1, key_.fee)
                            ),
                            POOL_INIT_CODE_HASH
                        )
                    )
                )
            )
        );
    }

    /**
     * @dev returns pool address.
     * @param token0_ token 0 address
     * @param token1_ token 1 address
     * @param fee_ fee of pool
     * @return poolAddr_ pool address
     */
    function getPoolAddress(
        address token0_,
        address token1_,
        uint24 fee_
    ) internal view returns (address poolAddr_) {
        poolAddr_ = computeAddress(
            nftManager.factory(),
            PoolKey({token0: token0_, token1: token1_, fee: fee_})
        );
    }

    /**
     * @dev Returns token pairs from NFT ID
     * @param NFTID_ NFT ID
     * @return token0_ token 0 address
     * @return token1_ token 1 address
     */
    function getNftTokenPairAddresses(uint256 NFTID_)
        internal
        view
        returns (address token0_, address token1_, uint24 fee_)
    {
        (
            ,
            ,
            token0_,
            token1_,
            fee_,
            ,
            ,
            ,
            ,
            ,
            ,
        ) = nftManager.positions(NFTID_);
    }
}

contract OraclesHelpers is UniswapHelpers {
    using SafeMath for uint256;

    /**
     * Returns the (latest price in USD in 18 decimals & token decimals) via chainlink oracle
     * @param token_ address of token
     * @return token price in USD in 18 decimals
     */
    function getPriceInUsd(address token_)
        internal
        view
        returns (uint256)
    {
        ChainLinkOracle oracle_ = ChainLinkOracle(_chainlinkOracle[token_]);
        return uint256(uint(oracle_.latestAnswer())) * 10**(18 - oracle_.decimals());
    }

    /**
     * Returns multiple tokens (latest price in USD in 18 decimals & token decimals) via chainlink oracle
     * @param tokens_ array of token addresses
     * @return amounts_ array of prices in USD in 18 decimals
     */
    function getPricesInUsd(address[] memory tokens_)
        internal
        view
        returns (uint256[] memory amounts_)
    {
        amounts_ = new uint256[](tokens_.length);
        for (uint256 i = 0; i < tokens_.length; i++) {
            amounts_[i] = getPriceInUsd(tokens_[i]);
        }
    }

    /**
     * returns the price of token0 w.r.t to price of token1. Eg:- 1 ETH = 3300 USDC.
     * @param token0_ Token for which the price needs to calculate
     * @param token1_ Token in which price is needed
     */
    function getOraclePrice(address token0_, address token1_)
        internal
        view
        returns (uint256)
    {
        uint price0_ = getPriceInUsd(token0_);
        uint price1_ = getPriceInUsd(token1_);
        uint token0Decimals_ = TokenInterface(token0_).decimals();
        uint token1Decimals_ = TokenInterface(token1_).decimals();
        return price0_.mul(1e18).div(price1_).mul(10 ** token1Decimals_).div(10 ** token0Decimals_);
    }

    /**
     * returns array of ticks at different checkpoints from Uniswap oracle.
     * @param pool_ Uniswap pool for which the price needs to be calculate.
     * @param secondsAgos_ Array of seconds ago (checkpoints, only admin can set it)
     * @return ticks_ array of ticks at different points
     */
    function getUniswapOracleTicksWithSecondsAgos(
        IUniswapV3Pool pool_,
        uint32[] memory secondsAgos_
    ) internal view returns (int56[] memory ticks_) {
        // eg: of secondAgos_ [0, 10, 30, 60, 120, 300]
        (int56[] memory tickCumulatives_, ) = pool_.observe(secondsAgos_);
        ticks_ = new int56[](secondsAgos_.length - 1);
        for (uint256 i = 1; i < secondsAgos_.length; i++) {
            ticks_[i - 1] = (tickCumulatives_[i] - tickCumulatives_[0]) / int56(uint56(secondsAgos_[i] - secondsAgos_[0]));
        }
    }

    /**
     * Verify ticks slippages to avoid any manipulated liquidations. Compares prices with past few check points.
     * Calls getUniswapOracleTicksWithSecondsAgos() to fetch prices at different checkpoints.
     * Throws if the tick slippage is more for that particular check point.
     * @param pool_ Uniswap pool.
     * @return currentTick_ current tick of the pool.
     */
    function verifyTicksSlippage(IUniswapV3Pool pool_)
        internal
        view
        returns (int24 currentTick_)
    {
        (, currentTick_, , , , , ) = pool_.slot0();
        TickCheck memory tickCheck_ = _tickCheck[address(pool_)];
        uint32[] memory secondsAgos_ = new uint32[](6);
        secondsAgos_[0] = 0;
        secondsAgos_[1] = tickCheck_.secsAgo1;
        secondsAgos_[2] = tickCheck_.secsAgo2;
        secondsAgos_[3] = tickCheck_.secsAgo3;
        secondsAgos_[4] = tickCheck_.secsAgo4;
        secondsAgos_[5] = tickCheck_.secsAgo5;
        int56[] memory ticks_ = getUniswapOracleTicksWithSecondsAgos(
            pool_,
            secondsAgos_
        );
        uint56[] memory dif_ = new uint56[](5);
        for (uint256 i = 0; i < 5; i++) {
            dif_[i] = currentTick_ < ticks_[i]
                ? uint56(ticks_[i] - currentTick_)
                : uint56(currentTick_ - ticks_[i]);
        }
        require(dif_[0] < tickCheck_.tickSlippage1, "excess-tick-slippage1");
        require(dif_[1] < tickCheck_.tickSlippage2, "excess-tick-slippage2");
        require(dif_[2] < tickCheck_.tickSlippage3, "excess-tick-slippage3");
        require(dif_[3] < tickCheck_.tickSlippage4, "excess-tick-slippage4");
        require(dif_[4] < tickCheck_.tickSlippage5, "excess-tick-slippage5");
    }

    /**
     * Converts tick to current price. token0 price w.r.t token1.
     * @param tick_ current tick
     * @return price_ price at current tick. token0 price w.r.t token1.
     */
    function tickToPrice(int24 tick_) internal pure returns (uint256 price_) {
        // TODO: verify the price in proper decimals
        uint256 sqrtPriceX96_ = TickMath.getSqrtRatioAtTick(int24(tick_));
        return
            uint256(sqrtPriceX96_).mul(uint256(sqrtPriceX96_)).mul(1e18) >>
            (96 * 2);
    }

    /**
     * Compares Uniswap & Chainlink oracle price to avoid manipulated liquidations.
     * Throws if the chainlink price in not within Uniswap slippage limit.
     * @param pool_ Uniswap pool
     * @param uniswapOraclePrice_ current tick
     */
    function comparePrice(IUniswapV3Pool pool_, uint256 uniswapOraclePrice_)
        internal
        view
    {
        address base_ = pool_.token0();
        address quote_ = pool_.token1();
        uint256 oraclePrice_ = getOraclePrice(base_, quote_);
        // TODO: Verify in what decimals should price slippage be
        uint256 slippageLimits_ = uniswapOraclePrice_
            .mul(_priceSlippage[address(pool_)])
            .div(10000);
        require(
            uniswapOraclePrice_.sub(slippageLimits_) < oraclePrice_,
            "price-outside-of-range"
        );
        require(
            uniswapOraclePrice_.add(slippageLimits_) > oraclePrice_,
            "price-outside-of-range"
        );
    }

    /**
     * Verifies oracles & ticks by calling the above functions.
     * Liquidate() function calls this to verify oracles before liquidations.
     * @param poolAddr_ Uniswap pool address
     */
    function verifyOracles(address poolAddr_) internal view {
        IUniswapV3Pool pool_ = IUniswapV3Pool(poolAddr_);
        int24 currentTick_ = verifyTicksSlippage(pool_);
        uint256 uniswapOraclePrice_ = tickToPrice(currentTick_);
        comparePrice(pool_, uniswapOraclePrice_);
    }

    function tokenDecimals(address[] memory tokens_) internal view returns (uint[] memory decimals_) {
        uint length_ = tokens_.length;
        decimals_ = new uint[](length_);
        for (uint i = 0; i < length_; i++) {
            decimals_[i] = TokenInterface(tokens_[i]).decimals();
        }
    }

}

contract CoreReadHelpers is OraclesHelpers {
    using SafeMath for uint256;

    /**
     * returns fee accrued by an NFT.
     * @param poolAddr_ address of Uniswap pool.
     * @param tickLower_ Lower tick of the NFT.
     * @param tickUpper_ Upper tick of the NFT.
     * @param liquidity_ Liquidity of the NFT.
     * @param feeGrowthInside0LastX128_ Last updated fee0 of the NFT.
     * @param feeGrowthInside1LastX128_ Last updated fee1 of the NFT.
     * @param tokensOwed0_ existing token 0 owed to an NFT (from withdraw liquidity or collected fees but not yet claimed by the NFT owner)
     * @param tokensOwed1_ existing token 0 owed to an NFT (from withdraw liquidity or collected fees but not yet claimed by the NFT owner)
     * @return amount0_ actual collected amount 0.
     * @return amount1_ actual collected amount 1.
     */
    function getFeeAccrued(
        address poolAddr_,
        int24 tickLower_,
        int24 tickUpper_,
        uint128 liquidity_,
        uint256 feeGrowthInside0LastX128_,
        uint256 feeGrowthInside1LastX128_,
        uint128 tokensOwed0_, // TODO: @bajram what is this?
        uint128 tokensOwed1_ // TODO: @bajram what is this?
    ) internal view returns (uint256 amount0_, uint256 amount1_) {
        IUniswapV3Pool pool_ = IUniswapV3Pool(poolAddr_);

        (
            ,
            uint256 feeGrowthInside0LastX128Total_,
            uint256 feeGrowthInside1LastX128Total_,
            ,

        ) = pool_.positions(
                PositionKey.compute(nftManagerAddr, tickLower_, tickUpper_)
            );

        tokensOwed0_ += uint128(
            FullMath.mulDiv(
                feeGrowthInside0LastX128Total_ - feeGrowthInside0LastX128_,
                liquidity_,
                FixedPoint128.Q128
            )
        );
        tokensOwed1_ += uint128(
            FullMath.mulDiv(
                feeGrowthInside1LastX128Total_ - feeGrowthInside1LastX128_,
                liquidity_,
                FixedPoint128.Q128
            )
        );

        amount0_ = tokensOwed0_;
        amount1_ = tokensOwed1_;
    }

    function getFeeAccruedWrapper(uint256 NFTID_, address poolAddr_)
        public
        view
        returns (uint256 amount0_, uint256 amount1_)
    {
        Position memory position_;
        uint128 liquidity_;
        (
            ,
            ,
            ,
            ,
            ,
            position_.tickLower_,
            position_.tickUpper_,
            liquidity_,
            position_.feeGrowthInside0LastX128_,
            position_.feeGrowthInside1LastX128_,
            position_.tokensOwed0_,
            position_.tokensOwed1_
        ) = nftManager.positions(NFTID_);
        (amount0_, amount1_) = getFeeAccrued(
            poolAddr_,
            position_.tickLower_,
            position_.tickUpper_,
            liquidity_,
            position_.feeGrowthInside0LastX128_,
            position_.feeGrowthInside1LastX128_,
            position_.tokensOwed0_,
            position_.tokensOwed1_
        );
    }

    /**
     * returns liquidity of the NFT in amount0 & amount1 (just liquidity without fee).
     * @param poolAddr_ address of Uniswap pool.
     * @param tickLower_ Lower tick of the NFT.
     * @param tickUpper_ Upper tick of the NFT.
     * @param liquidity_ Liquidity of the NFT.
     * @return amount0Total_ total amount 0.
     * @return amount1Total_ total amount 1.
     */
    function getNetNFTLiquidity(
        address poolAddr_,
        int24 tickLower_,
        int24 tickUpper_,
        uint128 liquidity_
    ) public view returns (uint256 amount0Total_, uint256 amount1Total_) {
        IUniswapV3Pool pool_ = IUniswapV3Pool(poolAddr_);
        (uint160 sqrtPriceX96_, , , , , , ) = pool_.slot0();
        // active liquidity in NFT
        (uint256 amount0_, uint256 amount1_) = LiquidityAmounts
            .getAmountsForLiquidity(
                sqrtPriceX96_,
                TickMath.getSqrtRatioAtTick(tickLower_),
                TickMath.getSqrtRatioAtTick(tickUpper_),
                liquidity_
            );
        amount0Total_ = amount0_;
        amount1Total_ = amount1_;
    }

    /**
     * returns net borrow balances. Returns array of borrowed token amounts.
     * @param NFTID_ NFT ID.
     * @param poolAddr_ address of Uniswap pool.
     * @return borrowBalances_ array of borrowed tokens amounts.
     */
    function getNetNFTDebt(uint256 NFTID_, address poolAddr_)
        public
        view
        returns (uint256[] memory borrowBalances_)
    {
        address[] memory markets_ = _poolMarkets[poolAddr_];
        borrowBalances_ = new uint256[](markets_.length);
        for (uint256 i = 0; i < markets_.length; i++) {
            address token_ = markets_[i];
            uint256 borrowBalRaw_ = _borrowBalRaw[NFTID_][token_];
            if (borrowBalRaw_ >= 0) {
                (, uint256 exchangePrice_) = liquidity.updateInterest(token_);
                borrowBalances_[i] = borrowBalRaw_.mul(exchangePrice_).div(1e18);
            } else {
                borrowBalances_[i] = 0;
            }
        }
    }

    /**
     * Get supply in USD. Extended & Normal.
     * @param amount0_ total amount 0.
     * @param amount1_ total amount 1.
     * @param amount0Normal_ amount 0 normal. (token0Collateral > token0Debt ? token0Collateral - token0Debt : 0)
     * @param amount1Normal_ amount 1 normal. (token1Collateral > token1Debt ? token1Collateral - token1Debt : 0)
     * @param token0Price_ token 0 price in USD in 18 decimals.
     * @param token1Price_ token 1 price in USD in 18 decimals.
     * @param token0Decimal_ token 0 decimals.
     * @param token1Decimal_ token 1 decimals.
     * @return totalSupplyInUsd_ total supply in USD (amount0 in USD + amount1 in USD)
     * @return totalNormalSupplyInUsd_ total normal supply in USD (amount0 normal in USD + amount1 normal in USD)
     */
    function getSupplyInUsd(
        uint256 amount0_,
        uint256 amount1_,
        uint256 amount0Normal_,
        uint256 amount1Normal_,
        uint256 token0Price_,
        uint256 token1Price_,
        uint256 token0Decimal_,
        uint256 token1Decimal_
    )
        internal
        pure
        returns (uint256 totalSupplyInUsd_, uint256 totalNormalSupplyInUsd_)
    {
        uint256 amount0InUsd_ = amount0_.mul(token0Price_).div(
            10**token0Decimal_
        );
        uint256 amount1InUsd_ = amount1_.mul(token1Price_).div(
            10**token1Decimal_
        );
        uint256 amount0NormalInUsd_ = amount0Normal_.mul(token0Price_).div(
            10**token0Decimal_
        );
        uint256 amount1NormalInUsd_ = amount1Normal_.mul(token1Price_).div(
            10**token1Decimal_
        );
        totalSupplyInUsd_ = amount0InUsd_.add(amount1InUsd_);
        totalNormalSupplyInUsd_ = amount0NormalInUsd_.add(amount1NormalInUsd_);
    }

    /**
     * Get debt in USD. Extended & Normal.
     * @param borrowAmts_ array of debt tokens amount.
     * @param borrow0Normal_ normal borrow amount of token0. (token0Debt > token0Collateral ? token0Debt - token0Collateral : 0)
     * @param borrow1Normal_ normal borrow amount of token1. (token0Debt > token0Collateral ? token0Debt - token0Collateral : 0)
     * @param tokenPrices_ array of borrow tokens price.
     * @param tokenDecimals_ array of borrow tokens decimals.
     * @return totalDebtInUsd_ total debt in USD. (sum of all borrow tokens in USD)
     * @return totalNormalDebtInUsd_ total normal debt in USD. (sum of all borrow tokens in USD other than token0 & token1, take borrow0Normal_ & borrow1Normal_ in USD for token0 & token1)
     */
    function getDebtInUsd(
        uint256[] memory borrowAmts_,
        uint256 borrow0Normal_,
        uint256 borrow1Normal_,
        uint256[] memory tokenPrices_,
        uint[] memory tokenDecimals_
    )
        internal
        pure
        returns (uint256 totalDebtInUsd_, uint256 totalNormalDebtInUsd_)
    {
        for (uint256 i = 0; i < borrowAmts_.length; i++) {
            uint256 tokenDebtInUsd_ = borrowAmts_[i].mul(tokenPrices_[i]).div(
                10**tokenDecimals_[i]
            );
            totalDebtInUsd_ = totalDebtInUsd_.add(tokenDebtInUsd_);
            if (i == 0) {
                totalNormalDebtInUsd_ = totalNormalDebtInUsd_.add(
                    borrow0Normal_.mul(tokenPrices_[i]).div(
                        10**tokenDecimals_[i]
                    )
                );
            } else if (i == 1) {
                totalNormalDebtInUsd_ = totalNormalDebtInUsd_.add(
                    borrow1Normal_.mul(tokenPrices_[i]).div(
                        10**tokenDecimals_[i]
                    )
                );
            } else {
                totalNormalDebtInUsd_ = totalNormalDebtInUsd_.add(tokenDebtInUsd_);
            }
        }
    }

    struct GetOverallPosInternalsStruct {
        uint256 amount0Normal;
        uint256 amount1Normal;
        uint256 borrow0Normal;
        uint256 borrow1Normal;
    }

    /**
     * Get overall user's NFT debt position.
     * @param amount0_ token0 total amount (active liquidity and fee accrued)
     * @param amount1_ token1 total amount (active liquidity and fee accrued)
     * @param borrowBals_ array of borrow tokens amounts.
     * @param poolAddr_ address of pool.
     * @return totalSupplyInUsd_ total supply in USD (amount0 in USD + amount1 in USD)
     * @return totalNormalSupplyInUsd_ total normal supply in USD (amount0 normal in USD + amount1 normal in USD)
     * @return totalBorrowInUsd_ total debt in USD. (sum of all borrow tokens in USD)
     * @return totalNormalBorrowInUsd_ total normal debt in USD. (sum of all borrow tokens in USD other than token0 & token1, take borrow0Normal_ & borrow1Normal_ in USD for token0 & token1)
     */
    function _getOverallPosition(
        uint256 amount0_,
        uint256 amount1_,
        uint256[] memory borrowBals_,
        address poolAddr_
    )
        internal
        view
        returns (
            uint256 totalSupplyInUsd_,
            uint256 totalNormalSupplyInUsd_,
            uint256 totalBorrowInUsd_,
            uint256 totalNormalBorrowInUsd_
        )
    {
        GetOverallPosInternalsStruct memory internalStruct_;
        if (amount0_ > borrowBals_[0]) {
            internalStruct_.amount0Normal = amount0_ - borrowBals_[0];
        } else {
            internalStruct_.borrow0Normal = borrowBals_[0] - amount0_;
        }
        if (amount1_ > borrowBals_[1]) {
            internalStruct_.amount1Normal = amount1_ - borrowBals_[1];
        } else {
            internalStruct_.borrow1Normal = borrowBals_[1] - amount1_;
        }
        address[] memory markets_ = _poolMarkets[poolAddr_];
        uint256[] memory tokenPrices_ = getPricesInUsd(markets_);
        uint[] memory tokenDecimals_ = tokenDecimals(markets_);
        (totalSupplyInUsd_, totalNormalSupplyInUsd_) = getSupplyInUsd(
            amount0_,
            amount1_,
            internalStruct_.amount0Normal,
            internalStruct_.amount1Normal,
            tokenPrices_[0],
            tokenPrices_[1],
            tokenDecimals_[0],
            tokenDecimals_[1]
        );
        (totalBorrowInUsd_, totalNormalBorrowInUsd_) = getDebtInUsd(
            borrowBals_,
            internalStruct_.borrow0Normal,
            internalStruct_.borrow1Normal,
            tokenPrices_,
            tokenDecimals_
        );
    }

    /**
     * Get overall user's NFT debt position used by most of the core functions and calls all the above functions to calculate.
     * @param NFTID_ NFT ID.
     * @return poolAddr_ address of pool.
     * @return token0_ address of token0.
     * @return token1_ address of token1.
     * @return liquidity_ liquidity of NFT.
     * @return totalSupplyInUsd_ total supply in USD (amount0 in USD + amount1 in USD)
     * @return totalNormalSupplyInUsd_ total normal supply in USD (amount0 normal in USD + amount1 normal in USD)
     * @return totalBorrowInUsd_ total debt in USD. (sum of all borrow tokens in USD)
     * @return totalNormalBorrowInUsd_ total normal debt in USD. (sum of all borrow tokens in USD other than token0 & token1, take borrow0Normal_ & borrow1Normal_ in USD for token0 & token1)
     */
    function getOverallPosition(uint256 NFTID_)
        public
        view
        returns (
            address poolAddr_,
            address token0_,
            address token1_,
            uint128 liquidity_,
            uint256 totalSupplyInUsd_,
            uint256 totalNormalSupplyInUsd_,
            uint256 totalBorrowInUsd_,
            uint256 totalNormalBorrowInUsd_
        )
    {
        Position memory position_;
        (
            ,
            ,
            token0_,
            token1_,
            position_.fee_,
            position_.tickLower_,
            position_.tickUpper_,
            liquidity_,
            ,
            ,
            ,

        ) = nftManager.positions(NFTID_);
        poolAddr_ = getPoolAddress(token0_, token1_, position_.fee_);
        (position_.amount0_, position_.amount1_) = getNetNFTLiquidity(
            poolAddr_,
            position_.tickLower_,
            position_.tickUpper_,
            liquidity_
        );
        (position_.feeAccrued0_, position_.feeAccrued1_) = getFeeAccruedWrapper(
            NFTID_,
            poolAddr_
        );
        uint256[] memory borrowBals_ = getNetNFTDebt(NFTID_, poolAddr_);
        (
            totalSupplyInUsd_,
            totalNormalSupplyInUsd_,
            totalBorrowInUsd_,
            totalNormalBorrowInUsd_
        ) = _getOverallPosition(
            position_.amount0_.add(position_.feeAccrued0_),
            position_.amount1_.add(position_.feeAccrued1_),
            borrowBals_,
            poolAddr_
        );
    }

    /**
     * Checks if user has any debt.
     * @param NFTID_ NFT ID
     * @return has_ true if has any debt.
     */
    function hasDebt(uint256 NFTID_) internal view returns (bool has_) {
        (
            ,
            ,
            address token0_,
            address token1_,
            uint24 fee_,
            ,
            ,
            ,
            ,
            ,
            ,

        ) = nftManager.positions(NFTID_);
        address pool_ = getPoolAddress(token0_, token1_, fee_);
        address[] memory markets_ = _poolMarkets[pool_];
        for (uint256 i = 0; i < markets_.length; i++) {
            has_ = _borrowBalRaw[NFTID_][markets_[i]] == 0 ? false : true;
            if (has_) break;
        }
    }

    /**
     * Liquidation check.
     * @param poolAddr_ pool address
     * @param totalSupplyInUsd_ total supply in USD (amount0 in USD + amount1 in USD)
     * @param totalNormalSupplyInUsd_ total normal supply in USD (amount0 normal in USD + amount1 normal in USD)
     * @param totalBorrowInUsd_ total debt in USD. (sum of all borrow tokens in USD)
     * @param totalNormalBorrowInUsd_ total normal debt in USD. (sum of all borrow tokens in USD other than token0 & token1, take borrow0Normal_ & borrow1Normal_ in USD for token0 & token1)
     * @return isOk_ true is not going to liquidate.
     */
    function liquidationCheck(
        address poolAddr_,
        uint256 totalSupplyInUsd_,
        uint256 totalBorrowInUsd_,
        uint256 totalNormalSupplyInUsd_,
        uint256 totalNormalBorrowInUsd_
    ) internal view returns (bool isOk_) {
        BorrowLimit memory borrowLimit_ = _borrowLimit[poolAddr_];
        isOk_ =
            totalBorrowInUsd_ <=
            totalSupplyInUsd_.mul(borrowLimit_.extended).div(10000) &&
            totalNormalBorrowInUsd_ <=
            totalNormalSupplyInUsd_.mul(borrowLimit_.normal).div(10000);
    }

}

contract Helpers is CoreReadHelpers {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /**
     * Gives approval of a token.
     * @param token_ token address
     * @param spender_ address which we need to approve
     * @param amount_ amount of approval
     */
    function approve(
        TokenInterface token_,
        address spender_,
        uint256 amount_
    ) internal {
        try token_.approve(spender_, amount_) {} catch {
            IERC20 tokenContract_ = IERC20(address(token_));
            tokenContract_.safeApprove(spender_, 0);
            tokenContract_.safeApprove(spender_, amount_);
        }
    }

    /**
     * Adds liquidity to an NFT.
     * @param NFTID_ NFT ID.
     * @param amount0_ amount0 to add.
     * @param amount1_ amount1 to add.
     * @param amount0Min_ amount0 minimum limit to add.
     * @param amount1Min_ amount1 minimum limit to add.
     * @param deadline_ deadline at which transaction should fail.
     * @return liquidity_ exact amount of liquidity added.
     * @return amount0Exact_ exact amount 0 added.
     * @return amount1Exact_ exact amount 1 added.
     */
    function _addLiquidity(
        uint256 NFTID_,
        uint256 amount0_,
        uint256 amount1_,
        uint256 amount0Min_,
        uint256 amount1Min_,
        uint256 deadline_
    )
        internal
        returns (
            uint128 liquidity_,
            uint256 amount0Exact_,
            uint256 amount1Exact_
        )
    {
        IncreaseLiquidityParams memory params_ = IncreaseLiquidityParams(
            NFTID_,
            amount0_,
            amount1_,
            amount0Min_,
            amount1Min_,
            deadline_
        );

        (liquidity_, amount0Exact_, amount1Exact_) = nftManager
            .increaseLiquidity(params_);
    }

    /**
     * remove Liquidity of an NFT
     * @param NFTID_ NFT ID.
     * @param liquidity_ liquidity to remove
     * @param amount0Min_ amount0 minimum limit.
     * @param amount1Min_ amount1 minimum limit.
     * @return amount0_ exact amount 0 removed.
     * @return amount1_ exact amount 1 removed.
     */
    function _decreaseLiquidity(
        uint256 NFTID_,
        uint256 liquidity_,
        uint256 amount0Min_,
        uint256 amount1Min_
    ) internal returns (uint256 amount0_, uint256 amount1_) {
        DecreaseLiquidityParams memory params_ = DecreaseLiquidityParams(
            NFTID_,
            uint128(liquidity_),
            amount0Min_,
            amount1Min_,
            block.timestamp
        );
        (amount0_, amount1_) = nftManager.decreaseLiquidity(params_);
    }

    /**
     * borrows a token. Only updates the storage, does not transfer borrow token to user.
     * @param NFTID_ NFT ID.
     * @param token_ token to borrow.
     * @param amount_ amount to borrow.
     */
    function _borrow(
        uint256 NFTID_,
        address token_,
        uint256 amount_
    ) internal {
        (, uint updateExchangePrice_) = liquidity.updateInterest(token_);
        uint256 amountRaw_ = amount_.mul(1e18).div(updateExchangePrice_);
        uint256 borrowBalRaw_ = _borrowBalRaw[NFTID_][token_];
        _borrowBalRaw[NFTID_][token_] = borrowBalRaw_.add(amountRaw_);
    }

    /**
     * payback debt on an NFT. type(uint).max for max payback.
     * @param NFTID_ NFT ID.
     * @param token_ token to payback.
     * @param amount_ amount to payback.
     * @return exactAmount_ exact amount payed back.
     */
    function _payback(
        uint256 NFTID_,
        address token_,
        uint256 amount_
    ) internal returns (uint256 exactAmount_) {
        (, uint updateExchangePrice_) = liquidity.updateInterest(token_);
        uint256 borrowBalRaw_ = _borrowBalRaw[NFTID_][token_];
        if (amount_ != type(uint256).max) {
            uint256 amountRaw_ = amount_
                .mul(1e18)
                .div(updateExchangePrice_);
            // throws is amountRaw_ is greater than borrowBalRaw_
            _borrowBalRaw[NFTID_][token_] = borrowBalRaw_.sub(amountRaw_);
            exactAmount_ = amount_;
        } else {
            exactAmount_ = borrowBalRaw_.mul(updateExchangePrice_).div(1e18);
            _borrowBalRaw[NFTID_][token_] = 0;
        }
    }

    /**
     * collect fees on an NFT
     * @param NFTID_ NFT ID.
     * @param amount0Max_ amount0 max to collect.
     * @param amount1Max_ amount1 max to collect.
     * @return amount0_ exact amount0 collected.
     * @return amount1_ exact amount1 collected.
     */
    function _collect(
        uint256 NFTID_,
        uint128 amount0Max_,
        uint128 amount1Max_
    ) internal returns (uint256 amount0_, uint256 amount1_) {
        CollectParams memory params_ = CollectParams(
            NFTID_,
            msg.sender,
            amount0Max_,
            amount1Max_
        );
        (amount0_, amount1_) = nftManager.collect(params_);
    }

}

File 3 of 19 : SafeMath.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

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

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

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

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

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

File 4 of 19 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

File 5 of 19 : SafeERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../../../utils/Address.sol";

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

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

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

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

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

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

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

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 6 of 19 : variables.sol
pragma solidity ^0.8.0;


import "../../common/ILiquidity.sol";

contract Variables {

    // status for re-entrancy. 1 = allow/non-entered, 2 = disallow/entered
    uint256 internal _status;

    ILiquidity constant internal liquidity = ILiquidity(address(0x4EE6eCAD1c2Dae9f525404De8555724e3c35d07B)); // TODO: add the core liquidity address

    // pool => bool. To enable a pool
    mapping (address => bool) internal poolEnabled_;

    // owner => NFT ID => borrow Position details
    mapping (address => mapping (uint => bool)) internal _position;

    // NFT ID => staked Position details
    mapping (uint => bool) internal _isStaked;

    // rewards accrued at the time of unstaking. NFTID -> token address -> reward amount
    mapping (uint => mapping(address => uint)) internal _rewardAccrued;

    // pool => minimum tick. Minimum tick difference a position should have to deposit (upperTick - lowerTick)
    mapping (address => uint) internal _minTick;

    // NFT ID => token => uint
    mapping (uint => mapping (address => uint)) internal _borrowBalRaw;

    // pool => token => bool
    mapping (address => mapping (address => bool)) internal _borrowAllowed;

    // pool => array or tokens. Market of borrow tokens for particular pool.
    // first 2 markets are always token0 & token1
    mapping(address => address[]) internal _poolMarkets;

    // normal. 8500 = 0.85.
    // extended. 9500 = 0.95.
    // extended meaning max totalborrow/totalsupply ratio
    // normal meaning canceling the same token borrow & supply and calculate ratio from rest of the token meaining
    // if NFT has 1 ETH & 4000 USDC (at 1 ETH = 4000 USDC) and debt of 0.5 ETH & 5000 USDC then the ratio would be
    // extended = (2000 + 5000) / (4000 + 4000) = 7/8
    // normal = (0 + 1000) / (2000) = 1/2
    struct BorrowLimit {
        uint128 normal;
        uint128 extended;
    }

    // pool address => Borrow limit
    mapping (address => BorrowLimit) internal _borrowLimit;

    // pool => _priceSlippage
    // 1 = 0.01%. 10000 = 100%
    // used to check Uniswap and chainlink price
    mapping (address => uint) internal _priceSlippage;

    // Tick checkpoints
    // 5 checkpoints Eg:-
    // Past 10 sec.
    // Past 30 sec.
    // Past 60 sec.
    // Past 120 sec.
    // Past 300 sec.
    struct TickCheck {
        uint24 tickSlippage1;
        uint24 secsAgo1;
        uint24 tickSlippage2;
        uint24 secsAgo2;
        uint24 tickSlippage3;
        uint24 secsAgo3;
        uint24 tickSlippage4;
        uint24 secsAgo4;
        uint24 tickSlippage5;
        uint24 secsAgo5;
    }

    // pool => TickCheck
    mapping (address => TickCheck) internal _tickCheck;

    // token => oracle contract. Price in USD.
    mapping (address => address) internal _chainlinkOracle;

}

File 7 of 19 : events.sol
pragma solidity ^0.8.0;


contract Events {

    event depositLog(address owner_, uint256 NFTID_);

    event withdrawLog(uint96 NFTID_);

    event addLiquidityLog(
        uint96 NFTID_,
        uint256 exactAmount0_,
        uint256 exactAmount1_,
        uint256 deadline_
    );

    event removeLiquidityLog(
        uint96 NFTID_,
        uint256 exactAmount0_,
        uint256 exactAmount1_
    );

    event borrowLog(
        uint96 NFTID_,
        address token_,
        uint256 amount_
    );

    event paybackLog(
        uint96 NFTID_,
        address token_,
        uint256 amount_
    );

    event collectFeeLog(
        uint96 NFTID_,
        uint256 amount0_,
        uint256 amount1_
    );

    event liquidateLog(
        uint96 NFTID_,
        uint exactAmount0_,
        uint exactAmount1_,
        address[] markets_,
        uint[] paybackAmts_
    );

}

File 8 of 19 : TickMath.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
        require(absTick <= uint256(uint24(MAX_TICK)), "T");

        uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
        if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
        if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
        if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
        if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
        if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
        if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
        if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
        if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
        if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
        if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
        if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
        if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
        if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
        if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
        if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
        if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
        if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
        if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

        if (tick > 0) ratio = type(uint256).max / ratio;

        // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
        // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
        // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
        sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
    }

    /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
    /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
    /// ever return.
    /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96
    /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio
    function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
        // second inequality must be < because the price can never reach the price at the max tick
        require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, "R");
        uint256 ratio = uint256(sqrtPriceX96) << 32;

        uint256 r = ratio;
        uint256 msb = 0;

        assembly {
            let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(5, gt(r, 0xFFFFFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(4, gt(r, 0xFFFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(3, gt(r, 0xFF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(2, gt(r, 0xF))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := shl(1, gt(r, 0x3))
            msb := or(msb, f)
            r := shr(f, r)
        }
        assembly {
            let f := gt(r, 0x1)
            msb := or(msb, f)
        }

        if (msb >= 128) r = ratio >> (msb - 127);
        else r = ratio << (127 - msb);

        int256 log_2 = (int256(msb) - 128) << 64;

        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(63, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(62, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(61, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(60, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(59, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(58, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(57, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(56, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(55, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(54, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(53, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(52, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(51, f))
            r := shr(f, r)
        }
        assembly {
            r := shr(127, mul(r, r))
            let f := shr(128, r)
            log_2 := or(log_2, shl(50, f))
        }

        int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number

        int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
        int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

        tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
    }
}

File 9 of 19 : FullMath.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        // 512-bit multiply [prod1 prod0] = a * b
        // Compute the product mod 2**256 and mod 2**256 - 1
        // then 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(a, b, not(0))
            prod0 := mul(a, b)
            prod1 := sub(sub(mm, prod0), lt(mm, prod0))
        }

        // Handle non-overflow cases, 256 by 256 division
        if (prod1 == 0) {
            require(denominator > 0);
            assembly {
                result := div(prod0, denominator)
            }
            return result;
        }

        // 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]
        // Compute remainder using mulmod
        uint256 remainder;
        assembly {
            remainder := mulmod(a, b, denominator)
        }
        // Subtract 256 bit number from 512 bit number
        assembly {
            prod1 := sub(prod1, gt(remainder, prod0))
            prod0 := sub(prod0, remainder)
        }

        // Factor powers of two out of denominator
        // Compute largest power of two divisor of denominator.
        // Always >= 1.
        uint256 twos = (type(uint256).max - denominator + 1) & denominator;
        // Divide denominator by power of two
        assembly {
            denominator := div(denominator, twos)
        }

        // Divide [prod1 prod0] by the factors of two
        assembly {
            prod0 := div(prod0, twos)
        }
        // Shift in bits from prod1 into prod0. For this we need
        // to flip `twos` such that it is 2**256 / twos.
        // If twos is zero, then it becomes one
        assembly {
            twos := add(div(sub(0, twos), twos), 1)
        }
        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
        // correct for four bits. That is, denominator * inv = 1 mod 2**4
        uint256 inv = (3 * denominator) ^ 2;
        // Now use 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.
        inv *= 2 - denominator * inv; // inverse mod 2**8
        inv *= 2 - denominator * inv; // inverse mod 2**16
        inv *= 2 - denominator * inv; // inverse mod 2**32
        inv *= 2 - denominator * inv; // inverse mod 2**64
        inv *= 2 - denominator * inv; // inverse mod 2**128
        inv *= 2 - denominator * inv; // 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 precoditions 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 * inv;
        return result;
    }

    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        result = mulDiv(a, b, denominator);
        if (mulmod(a, b, denominator) > 0) {
            require(result < type(uint256).max);
            result++;
        }
    }
}

File 10 of 19 : FixedPoint128.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.4.0;

/// @title FixedPoint128
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
library FixedPoint128 {
    uint256 internal constant Q128 = 0x100000000000000000000000000000000;
}

File 11 of 19 : LiquidityAmounts.sol
pragma solidity >=0.5.0;
import "./FullMath.sol";
import "./FixedPoint96.sol";
library LiquidityAmounts {
    function toUint128(uint256 x) private pure returns (uint128 y) {
        require((y = uint128(x)) == x);
    }

    function getLiquidityForAmount0(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96);
        return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
    }

    function getLiquidityForAmount1(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
        return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96));
    }

    function getLiquidityForAmounts(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint256 amount0,
        uint256 amount1
    ) internal pure returns (uint128 liquidity) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
            uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);

            liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
        } else {
            liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
        }
    }

    function getAmount0ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return
            FullMath.mulDiv(
                uint256(liquidity) << FixedPoint96.RESOLUTION,
                sqrtRatioBX96 - sqrtRatioAX96,
                sqrtRatioBX96
            ) / sqrtRatioAX96;
    }

    function getAmount1ForLiquidity(
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
    }

    function getAmountsForLiquidity(
        uint160 sqrtRatioX96,
        uint160 sqrtRatioAX96,
        uint160 sqrtRatioBX96,
        uint128 liquidity
    ) internal pure returns (uint256 amount0, uint256 amount1) {
        if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

        if (sqrtRatioX96 <= sqrtRatioAX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        } else if (sqrtRatioX96 < sqrtRatioBX96) {
            amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
        } else {
            amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
        }
    }
}

File 12 of 19 : PositionKey.sol
pragma solidity >=0.5.0;

library PositionKey {
    function compute(
        address owner,
        int24 tickLower,
        int24 tickUpper
    ) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(owner, tickLower, tickUpper));
    }
}

File 13 of 19 : interfaces.sol
pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '@uniswap/v3-core/contracts/interfaces/IERC20Minimal.sol';
import '@uniswap/v3-periphery/contracts/interfaces/IMulticall.sol';

interface TokenInterface {
    function approve(address, uint256) external;

    function decimals() external view returns (uint256);
}

interface INonfungiblePositionManager {
    function positions(uint256 tokenId)
        external
        view
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    function factory() external view returns (address);

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) external;

    function increaseLiquidity(IncreaseLiquidityParams calldata params)
        external
        payable
        returns (
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        );

    function collect(CollectParams calldata params)
        external
        payable
        returns (uint256 amount0, uint256 amount1);

    function decreaseLiquidity(DecreaseLiquidityParams calldata params)
        external
        payable
        returns (uint256 amount0, uint256 amount1);

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

    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
}

interface IUniswapV3Pool {
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

    function token0() external view returns (address);

    function token1() external view returns (address);

    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (
            int56[] memory tickCumulatives,
            uint160[] memory secondsPerLiquidityCumulativeX128s
        );

    function positions(bytes32 key)
        external
        view
        returns (
            uint128 _liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );
}

struct IncreaseLiquidityParams {
    uint256 tokenId;
    uint256 amount0Desired;
    uint256 amount1Desired;
    uint256 amount0Min;
    uint256 amount1Min;
    uint256 deadline;
}

/// @title Uniswap V3 Staker Interface
/// @notice Allows staking nonfungible liquidity tokens in exchange for reward tokens
interface IUniswapV3Staker is IERC721Receiver, IMulticall {
    /// @param rewardToken The token being distributed as a reward
    /// @param pool The Uniswap V3 pool
    /// @param startTime The time when the incentive program begins
    /// @param endTime The time when rewards stop accruing
    /// @param refundee The address which receives any remaining reward tokens when the incentive is ended
    struct IncentiveKey {
        IERC20Minimal rewardToken;
        IUniswapV3Pool pool;
        uint256 startTime;
        uint256 endTime;
        address refundee;
    }

    /// @notice The nonfungible position manager with which this staking contract is compatible
    function nonfungiblePositionManager() external view returns (INonfungiblePositionManager);

    /// @notice Represents a staking incentive
    /// @param incentiveId The ID of the incentive computed from its parameters
    /// @return totalRewardUnclaimed The amount of reward token not yet claimed by users
    /// @return totalSecondsClaimedX128 Total liquidity-seconds claimed, represented as a UQ32.128
    /// @return numberOfStakes The count of deposits that are currently staked for the incentive
    function incentives(bytes32 incentiveId)
        external
        view
        returns (
            uint256 totalRewardUnclaimed,
            uint160 totalSecondsClaimedX128,
            uint96 numberOfStakes
        );

    /// @notice Returns information about a deposited NFT
    /// @return owner The owner of the deposited NFT
    /// @return numberOfStakes Counter of how many incentives for which the liquidity is staked
    /// @return tickLower The lower tick of the range
    /// @return tickUpper The upper tick of the range
    function deposits(uint256 tokenId)
        external
        view
        returns (
            address owner,
            uint48 numberOfStakes,
            int24 tickLower,
            int24 tickUpper
        );

    /// @notice Returns information about a staked liquidity NFT
    /// @param tokenId The ID of the staked token
    /// @param incentiveId The ID of the incentive for which the token is staked
    /// @return secondsPerLiquidityInsideInitialX128 secondsPerLiquidity represented as a UQ32.128
    /// @return liquidity The amount of liquidity in the NFT as of the last time the rewards were computed
    function stakes(uint256 tokenId, bytes32 incentiveId)
        external
        view
        returns (uint160 secondsPerLiquidityInsideInitialX128, uint128 liquidity);

    /// @notice Returns amounts of reward tokens owed to a given address according to the last time all stakes were updated
    /// @param rewardToken The token for which to check rewards
    /// @param owner The owner for which the rewards owed are checked
    /// @return rewardsOwed The amount of the reward token claimable by the owner
    function rewards(IERC20Minimal rewardToken, address owner) external view returns (uint256 rewardsOwed);

    /// @notice Creates a new liquidity mining incentive program
    /// @param key Details of the incentive to create
    /// @param reward The amount of reward tokens to be distributed
    function createIncentive(IncentiveKey memory key, uint256 reward) external;

    /// @notice Ends an incentive after the incentive end time has passed and all stakes have been withdrawn
    /// @param key Details of the incentive to end
    /// @return refund The remaining reward tokens when the incentive is ended
    function endIncentive(IncentiveKey memory key) external returns (uint256 refund);

    /// @notice Withdraws a Uniswap V3 LP token `tokenId` from this contract to the recipient `to`
    /// @param tokenId The unique identifier of an Uniswap V3 LP token
    /// @param to The address where the LP token will be sent
    /// @param data An optional data array that will be passed along to the `to` address via the NFT safeTransferFrom
    function withdrawToken(
        uint256 tokenId,
        address to,
        bytes memory data
    ) external;

    /// @notice Stakes a Uniswap V3 LP token
    /// @param key The key of the incentive for which to stake the NFT
    /// @param tokenId The ID of the token to stake
    function stakeToken(IncentiveKey memory key, uint256 tokenId) external;

    /// @notice Unstakes a Uniswap V3 LP token
    /// @param key The key of the incentive for which to unstake the NFT
    /// @param tokenId The ID of the token to unstake
    function unstakeToken(IncentiveKey memory key, uint256 tokenId) external;

    /// @notice Transfers `amountRequested` of accrued `rewardToken` rewards from the contract to the recipient `to`
    /// @param rewardToken The token being distributed as a reward
    /// @param to The address where claimed rewards will be sent to
    /// @param amountRequested The amount of reward tokens to claim. Claims entire reward amount if set to 0.
    /// @return reward The amount of reward tokens claimed
    function claimReward(
        IERC20Minimal rewardToken,
        address to,
        uint256 amountRequested
    ) external returns (uint256 reward);

}

struct CollectParams {
    uint256 tokenId;
    address recipient;
    uint128 amount0Max;
    uint128 amount1Max;
}

struct DecreaseLiquidityParams {
    uint256 tokenId;
    uint128 liquidity;
    uint256 amount0Min;
    uint256 amount1Min;
    uint256 deadline;
}

interface ChainLinkOracle {
    function latestAnswer() external view returns (int256);
    function decimals() external view returns (uint8);
}

File 14 of 19 : ILiquidity.sol
pragma solidity ^0.8.0;


interface ILiquidity {

    function supply(
        address token_,
        uint amount_
    ) external returns (
        uint newSupplyRate_,
        uint newBorrowRate_,
        uint newSupplyExchangePrice_,
        uint newBorrowExchangePrice_
    );

    function withdraw(
        address token_,
        uint amount_
    ) external returns (
        uint newSupplyRate_,
        uint newBorrowRate_,
        uint newSupplyExchangePrice_,
        uint newBorrowExchangePrice_
    );

    function borrow(
        address token_,
        uint amount_
    ) external returns (
        uint newSupplyRate_,
        uint newBorrowRate_,
        uint newSupplyExchangePrice_,
        uint newBorrowExchangePrice_
    );

    function payback(
        address token_,
        uint amount_
    ) external returns (
        uint newSupplyRate_,
        uint newBorrowRate_,
        uint newSupplyExchangePrice_,
        uint newBorrowExchangePrice_
    );

    function updateInterest(
        address token_
    ) external view returns (
        uint newSupplyExchangePrice,
        uint newBorrowExchangePrice
    );

    function isProtocol(address protocol_) external view returns (bool);

    function protocolSupplyLimit(address protocol_, address token_) external view returns (uint256);

    function protocolBorrowLimit(address protocol_, address token_) external view returns (uint256);

    function totalSupplyRaw(address token_) external view returns (uint256);

    function totalBorrowRaw(address token_) external view returns (uint256);

    function protocolRawSupply(address protocol_, address token_) external view returns (uint256);

    function protocolRawBorrow(address protocol_, address token_) external view returns (uint256);

    struct Rates {
        uint96 lastSupplyExchangePrice; // last stored exchange price. Increases overtime.
        uint96 lastBorrowExchangePrice; // last stored exchange price. Increases overtime.
        uint48 lastUpdateTime; // in sec
        uint16 utilization; // utilization. 10000 = 100%
    }

    function rate(address token_) external view returns (Rates memory);

}

File 15 of 19 : Address.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

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

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

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

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

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

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

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

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

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

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

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

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

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

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

File 16 of 19 : FixedPoint96.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.4.0;

/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
    uint8 internal constant RESOLUTION = 96;
    uint256 internal constant Q96 = 0x1000000000000000000000000;
}

File 17 of 19 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

File 18 of 19 : IERC20Minimal.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Minimal ERC20 interface for Uniswap
/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
interface IERC20Minimal {
    /// @notice Returns the balance of a token
    /// @param account The account for which to look up the number of tokens it has, i.e. its balance
    /// @return The number of tokens held by the account
    function balanceOf(address account) external view returns (uint256);

    /// @notice Transfers the amount of token from the `msg.sender` to the recipient
    /// @param recipient The account that will receive the amount transferred
    /// @param amount The number of tokens to send from the sender to the recipient
    /// @return Returns true for a successful transfer, false for an unsuccessful transfer
    function transfer(address recipient, uint256 amount) external returns (bool);

    /// @notice Returns the current allowance given to a spender by an owner
    /// @param owner The account of the token owner
    /// @param spender The account of the token spender
    /// @return The current allowance granted by `owner` to `spender`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
    /// @param spender The account which will be allowed to spend a given amount of the owners tokens
    /// @param amount The amount of tokens allowed to be used by `spender`
    /// @return Returns true for a successful approval, false for unsuccessful
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
    /// @param sender The account from which the transfer will be initiated
    /// @param recipient The recipient of the transfer
    /// @param amount The amount of the transfer
    /// @return Returns true for a successful transfer, false for unsuccessful
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
    /// @param from The account from which the tokens were sent, i.e. the balance decreased
    /// @param to The account to which the tokens were sent, i.e. the balance increased
    /// @param value The amount of tokens that were transferred
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
    /// @param owner The account that approved spending of its tokens
    /// @param spender The account for which the spending allowance was modified
    /// @param value The new allowance from the owner to the spender
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 19 of 19 : IMulticall.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

/// @title Multicall interface
/// @notice Enables calling multiple methods in a single call to the contract
interface IMulticall {
    /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
    /// @dev The `msg.value` should not be trusted for any method callable from multicall.
    /// @param data The encoded function data for each of the calls to make to this contract
    /// @return results The results from each of the calls passed in via data
    function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}

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

Contract Security Audit

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"exactAmount0_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exactAmount1_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"deadline_","type":"uint256"}],"name":"addLiquidityLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"},{"indexed":false,"internalType":"address","name":"token_","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"borrowLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"amount0_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1_","type":"uint256"}],"name":"collectFeeLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner_","type":"address"},{"indexed":false,"internalType":"uint256","name":"NFTID_","type":"uint256"}],"name":"depositLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"exactAmount0_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exactAmount1_","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"markets_","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"paybackAmts_","type":"uint256[]"}],"name":"liquidateLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"},{"indexed":false,"internalType":"address","name":"token_","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"paybackLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"exactAmount0_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exactAmount1_","type":"uint256"}],"name":"removeLiquidityLog","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"withdrawLog","type":"event"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"},{"internalType":"uint256","name":"amount0_","type":"uint256"},{"internalType":"uint256","name":"amount1_","type":"uint256"},{"internalType":"uint256","name":"minAmount0_","type":"uint256"},{"internalType":"uint256","name":"minAmount1_","type":"uint256"},{"internalType":"uint256","name":"deadline_","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"exactAmount0_","type":"uint256"},{"internalType":"uint256","name":"exactAmount1_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"},{"internalType":"address","name":"token_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"borrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken_","type":"address"},{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"claimRewards","outputs":[{"internalType":"uint256","name":"rewards_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"collectFees","outputs":[{"internalType":"uint256","name":"amount0_","type":"uint256"},{"internalType":"uint256","name":"amount1_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"depositNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"NFTID_","type":"uint256"},{"internalType":"address","name":"poolAddr_","type":"address"}],"name":"getFeeAccruedWrapper","outputs":[{"internalType":"uint256","name":"amount0_","type":"uint256"},{"internalType":"uint256","name":"amount1_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"NFTID_","type":"uint256"},{"internalType":"address","name":"poolAddr_","type":"address"}],"name":"getNetNFTDebt","outputs":[{"internalType":"uint256[]","name":"borrowBalances_","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddr_","type":"address"},{"internalType":"int24","name":"tickLower_","type":"int24"},{"internalType":"int24","name":"tickUpper_","type":"int24"},{"internalType":"uint128","name":"liquidity_","type":"uint128"}],"name":"getNetNFTLiquidity","outputs":[{"internalType":"uint256","name":"amount0Total_","type":"uint256"},{"internalType":"uint256","name":"amount1Total_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"NFTID_","type":"uint256"}],"name":"getOverallPosition","outputs":[{"internalType":"address","name":"poolAddr_","type":"address"},{"internalType":"address","name":"token0_","type":"address"},{"internalType":"address","name":"token1_","type":"address"},{"internalType":"uint128","name":"liquidity_","type":"uint128"},{"internalType":"uint256","name":"totalSupplyInUsd_","type":"uint256"},{"internalType":"uint256","name":"totalNormalSupplyInUsd_","type":"uint256"},{"internalType":"uint256","name":"totalBorrowInUsd_","type":"uint256"},{"internalType":"uint256","name":"totalNormalBorrowInUsd_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"},{"internalType":"uint256","name":"amount0Min_","type":"uint256"},{"internalType":"uint256","name":"amount1Min_","type":"uint256"},{"internalType":"address[]","name":"rewardTokens_","type":"address[]"},{"internalType":"uint256[]","name":"startTime_","type":"uint256[]"},{"internalType":"uint256[]","name":"endTime_","type":"uint256[]"},{"internalType":"address[]","name":"refundee_","type":"address[]"}],"name":"liquidate","outputs":[{"components":[{"internalType":"uint256","name":"exactAmount0","type":"uint256"},{"internalType":"uint256","name":"exactAmount1","type":"uint256"},{"internalType":"address[]","name":"markets","type":"address[]"},{"internalType":"uint256[]","name":"paybackAmts","type":"uint256[]"}],"internalType":"struct UserModule.LiquidateVariables","name":"v_","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"},{"internalType":"uint256","name":"amount0Min_","type":"uint256"},{"internalType":"uint256","name":"amount1Min_","type":"uint256"}],"name":"liquidate","outputs":[{"internalType":"uint256","name":"exactAmount0_","type":"uint256"},{"internalType":"uint256","name":"exactAmount1_","type":"uint256"},{"internalType":"address[]","name":"markets_","type":"address[]"},{"internalType":"uint256[]","name":"paybackAmts_","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"},{"internalType":"address","name":"token_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"payback","outputs":[{"internalType":"uint256","name":"exactAmount_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"},{"internalType":"uint256","name":"liquidity_","type":"uint256"},{"internalType":"uint256","name":"amount0Min_","type":"uint256"},{"internalType":"uint256","name":"amount1Min_","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"exactAmount0_","type":"uint256"},{"internalType":"uint256","name":"exactAmount1_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken_","type":"address"},{"internalType":"uint256","name":"startTime_","type":"uint256"},{"internalType":"uint256","name":"endTime_","type":"uint256"},{"internalType":"address","name":"refundee_","type":"address"},{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"rewardToken_","type":"address"},{"internalType":"uint256","name":"startTime_","type":"uint256"},{"internalType":"uint256","name":"endTime_","type":"uint256"},{"internalType":"address","name":"refundee_","type":"address"},{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"NFTID_","type":"uint96"}],"name":"withdrawNFT","outputs":[],"stateMutability":"nonpayable","type":"function"}]

608060405234801561001057600080fd5b50615f9a80620000216000396000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c80634be109a7116100a2578063bd9885ed11610071578063bd9885ed146102cb578063c815cbba146102de578063cc9774a2146102fe578063da0fd44114610311578063dee4dea01461032457600080fd5b80634be109a7146102615780635b7ed72614610284578063610c76b01461029757806366a00121146102b857600080fd5b80632ee52af4116100e95780632ee52af4146101ab5780633402d6ea146101c0578063369c2eed146101d35780633be72fe7146101e65780634679362c146101f957600080fd5b80630d0cf5661461011b578063150b7a02146101445780632099e973146101705780632882274214610198575b600080fd5b61012e610129366004615668565b610337565b60405161013b9190615a1c565b60405180910390f35b61015761015236600461508f565b610548565b6040516001600160e01b0319909116815260200161013b565b61018361017e366004615739565b61063f565b6040805192835260208301919091520161013b565b6101836101a6366004615457565b610719565b6101be6101b93660046155f2565b610812565b005b6101be6101ce36600461518a565b610a9a565b6101836101e1366004615774565b610b2a565b6101836101f43660046154f1565b610ce0565b61020c61020736600461543e565b610db9565b604080516001600160a01b03998a168152978916602089015295909716948601949094526001600160801b03929092166060850152608084015260a083015260c082015260e08101919091526101000161013b565b61027461026f366004615633565b610f1f565b60405161013b9493929190615a72565b6101be61029236600461518a565b610f6e565b6102aa6102a53660046155f2565b610fe8565b60405190815260200161013b565b6101836102c636600461512e565b611163565b6101be6102d93660046154f1565b611216565b6102f16102ec366004615457565b61132f565b60405161013b919061588b565b6101be61030c3660046154f1565b611515565b6102aa61031f3660046151e9565b611622565b6101be6103323660046154f1565b6116d8565b6103626040518060800160405280600081526020016000815260200160608152602001606081525090565b6000546002141561038e5760405162461bcd60e51b8152600401610385906159a6565b60405180910390fd5b600260009081556001600160601b03891681526003602052604090205460ff166103eb5760405162461bcd60e51b815260206004820152600e60248201526d1391950b5b9bdd0b5cdd185ad95960921b6044820152606401610385565b60005b855181101561047a5761046886828151811061040c5761040c615ec7565b602002602001015186838151811061042657610426615ec7565b602002602001015186848151811061044057610440615ec7565b602002602001015186858151811061045a5761045a615ec7565b60200260200101518d61187a565b8061047281615e2c565b9150506103ee565b50604051633c423f0b60e01b81526001600160601b0389166004820152306024820152606060448201526000606482015273e34139463ba50bd61336e0c446bd8c0867c6fe6590633c423f0b90608401600060405180830381600087803b1580156104e457600080fd5b505af11580156104f8573d6000803e3d6000fd5b5050506001600160601b0389166000908152600360205260409020805460ff1916905550610527888888611b11565b60608501526040840152602083015281526001600055979650505050505050565b600080546002141561056c5760405162461bcd60e51b8152600401610385906159a6565b60026000556001600160a01b03868116908616146105cc5760405162461bcd60e51b815260206004820152601c60248201527f6f70657261746f722d73686f756c642d62652d7468652d6f776e6572000000006044820152606401610385565b33600080516020615f458339815191521461061e5760405162461bcd60e51b8152602060048201526012602482015271139bdd0b555b9a5cddd85c0b558ccb53919560721b6044820152606401610385565b6106288585611fbc565b50630a85bd0160e11b600160005595945050505050565b600080600054600214156106655760405162461bcd60e51b8152600401610385906159a6565b60026000818155338152602091825260408082206001600160601b038a1683529092522054869060ff166106ab5760405162461bcd60e51b815260040161038590615980565b6106b98787878760006121a8565b604080516001600160601b038b1681526020810184905290810182905291945092507f43d9ed6cfdf178be6277cd476e8779d2699d9f5faf4e6a3192ad16f3aa0eb10c9060600160405180910390a1506001600055909590945092505050565b600080610724614e5e565b60405163133f757160e31b815260048101869052600090600080516020615f45833981519152906399fbab88906024016101806040518083038186803b15801561076d57600080fd5b505afa158015610781573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a5919061550e565b6001600160801b0390811660c08f01819052911660a08e0181905260808e0183905260608e01849052600295860b860b60408f0181905296860b90950b60208e01819052939b506108059a508f9950929750939550899450929190612335565b9097909650945050505050565b3360009081526002602090815260408083206001600160601b0387168452909152902054839060ff166108575760405162461bcd60e51b815260040161038590615980565b6000546002141561087a5760405162461bcd60e51b8152600401610385906159a6565b60026000556108936001600160601b038516848461243d565b60008060008060006108ad896001600160601b0316610db9565b9750975097509750505050945060076000866001600160a01b03166001600160a01b031681526020019081526020016000206000896001600160a01b03166001600160a01b0316815260200190815260200160002060009054906101000a900460ff1661095c5760405162461bcd60e51b815260206004820152601c60248201527f746f6b656e2d6e6f742d616c6c6f7765642d666f722d626f72726f77000000006044820152606401610385565b600061096b8686858786612545565b90508061098a5760405162461bcd60e51b81526004016103859061591b565b604051634b8a352960e01b81526001600160a01b038a16600482015260248101899052734ee6ecad1c2dae9f525404de8555724e3c35d07b90634b8a352990604401608060405180830381600087803b1580156109e657600080fd5b505af11580156109fa573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a1e91906154a0565b50610a37925050506001600160a01b038a16338a6125d0565b604080516001600160601b038c1681526001600160a01b038b1660208201529081018990527fae954a52856ee9eebad474cf799fe96184132a4aaebd705bebb71cc57b862c6c9060600160405180910390a1505060016000555050505050505050565b3360009081526002602090815260408083206001600160601b0385168452909152902054819060ff16610adf5760405162461bcd60e51b815260040161038590615980565b60005460021415610b025760405162461bcd60e51b8152600401610385906159a6565b6002600055610b1d868686866001600160601b038716612638565b5050600160005550505050565b60008060005460021415610b505760405162461bcd60e51b8152600401610385906159a6565b6002600090815580610b6a6001600160601b038b16612708565b5090925090508181610b876001600160a01b03831633308e6127a4565b610b9c6001600160a01b03821633308d6127a4565b610bbe6001600160a01b038316600080516020615f458339815191528d6127e2565b610be06001600160a01b038216600080516020615f458339815191528c6127e2565b610bf78c6001600160601b03168c8c8c8c8c612906565b909750955050888611610c1c5760405162461bcd60e51b815260040161038590615952565b878511610c3b5760405162461bcd60e51b815260040161038590615952565b610c5a33610c49888e615da9565b6001600160a01b03851691906125d0565b610c7933610c68878d615da9565b6001600160a01b03841691906125d0565b604080516001600160601b038e16815260208101889052908101869052606081018890527fff84d8c60e2ca19e97bffcc0124da8ba8c2fd67f5436b7ef405f12223a4338e69060800160405180910390a15050600160005550919890975095505050505050565b3360009081526002602090815260408083206001600160601b03851684529091528120548190839060ff16610d275760405162461bcd60e51b815260040161038590615980565b60005460021415610d4a5760405162461bcd60e51b8152600401610385906159a6565b6002600081905550610d5d8460006129ee565b604080516001600160601b03881681526020810184905290810182905291945092507fce57ebf3d136286f734d79b7e7dd71583256cb34c9ef5c4756905f00371b241e9060600160405180910390a15060016000559092909150565b600080600080600080600080610dcd614e5e565b60405163133f757160e31b8152600481018b9052600080516020615f45833981519152906399fbab88906024016101806040518083038186803b158015610e1357600080fd5b505afa158015610e27573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e4b919061550e565b50505050600291820b820b60408a015291810b900b602088015262ffffff909116808752929c50909a509850610e8692508a91508990612b4e565b9850610e9c898260200151836040015189611163565b61010083015260e0820152610eb18a8a610719565b6101408301526101208201526000610ec98b8b61132f565b9050610f06610eea8361012001518460e00151612c1190919063ffffffff16565b610140840151610100850151610eff91612c11565b838d612c26565b9c9e9b9d50999b989a9199909890975095509350505050565b60008060608060005460021415610f485760405162461bcd60e51b8152600401610385906159a6565b6002600055610f58878787611b11565b6001600055929a91995097509095509350505050565b3360009081526002602090815260408083206001600160601b0385168452909152902054819060ff16610fb35760405162461bcd60e51b815260040161038590615980565b60005460021415610fd65760405162461bcd60e51b8152600401610385906159a6565b6002600055610b1d868686868661187a565b600080546002141561100c5760405162461bcd60e51b8152600401610385906159a6565b6002600055826110266001600160601b0386168285612e8d565b91508115611104576110436001600160a01b0382163330856127a4565b61106b6001600160a01b038216734ee6ecad1c2dae9f525404de8555724e3c35d07b846127e2565b6040516306bdb15760e31b81526001600160a01b038516600482015260248101839052734ee6ecad1c2dae9f525404de8555724e3c35d07b906335ed8ab890604401608060405180830381600087803b1580156110c757600080fd5b505af11580156110db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ff91906154a0565b505050505b604080516001600160601b03871681526001600160a01b03861660208201529081018490527f2e86ebfe8cbdd92af9f2ff3242909dee6b7467eff85b836ead7bac76319c45dd9060600160405180910390a15060016000559392505050565b60008060008690506000816001600160a01b0316633850c7bd6040518163ffffffff1660e01b815260040160e06040518083038186803b1580156111a657600080fd5b505afa1580156111ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111de91906153b3565b5050505050509050600080611205836111f68b612fe2565b6111ff8b612fe2565b8a6133f9565b909b909a5098505050505050505050565b3360009081526002602090815260408083206001600160601b0385168452909152902054819060ff1661125b5760405162461bcd60e51b815260040161038590615980565b6000546002141561127e5760405162461bcd60e51b8152600401610385906159a6565b60026000908155604051633c423f0b60e01b81526001600160601b038416600482015230602482015260606044820152606481019190915273e34139463ba50bd61336e0c446bd8c0867c6fe6590633c423f0b90608401600060405180830381600087803b1580156112ef57600080fd5b505af1158015611303573d6000803e3d6000fd5b5050506001600160601b039092166000908152600360205260408120805460ff19169055600190555050565b6001600160a01b038116600090815260086020908152604080832080548251818502810185019093528083526060949383018282801561139857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161137a575b50505050509050805167ffffffffffffffff8111156113b9576113b9615edd565b6040519080825280602002602001820160405280156113e2578160200160208202803683370190505b50915060005b815181101561150d57600082828151811061140557611405615ec7565b60209081029190910181015160008881526006835260408082206001600160a01b0384168084529452808220549051637b3c5da760e01b815260048101949094529193509091734ee6ecad1c2dae9f525404de8555724e3c35d07b90637b3c5da790602401604080518083038186803b15801561148157600080fd5b505afa158015611495573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114b9919061547c565b91506114d99050670de0b6b3a76400006114d38484613495565b906134a1565b8685815181106114eb576114eb615ec7565b602002602001018181525050505050808061150590615e2c565b9150506113e8565b505092915050565b3360009081526002602090815260408083206001600160601b0385168452909152902054819060ff1661155a5760405162461bcd60e51b815260040161038590615980565b6000546002141561157d5760405162461bcd60e51b8152600401610385906159a6565b600260009081556001600160601b0383168082526003602052604091829020805460ff191660011790559051632142170760e11b815230600482015273e34139463ba50bd61336e0c446bd8c0867c6fe6560248201526044810191909152600080516020615f45833981519152906342842e0e90606401600060405180830381600087803b15801561160e57600080fd5b505af1158015610b1d573d6000803e3d6000fd5b3360009081526002602090815260408083206001600160601b0385168452909152812054829060ff166116675760405162461bcd60e51b815260040161038590615980565b6000546002141561168a5760405162461bcd60e51b8152600401610385906159a6565b600260009081556001600160601b03841681526004602090815260408083206001600160a01b03881684529091528120805491905591506116cc8433846134ad565b50600160005592915050565b3360009081526002602090815260408083206001600160601b0385168452909152902054819060ff1661171d5760405162461bcd60e51b815260040161038590615980565b600054600214156117405760405162461bcd60e51b8152600401610385906159a6565b600260009081556117596001600160601b038416613549565b9050801561179c5760405162461bcd60e51b815260206004820152601060248201526f0646562742d73686f756c642d62652d360841b6044820152606401610385565b3360008181526002602090815260408083206001600160601b038816808552925291829020805460ff191690559051632142170760e11b815230600482015260248101929092526044820152600080516020615f45833981519152906342842e0e90606401600060405180830381600087803b15801561181b57600080fd5b505af115801561182f573d6000803e3d6000fd5b50506040516001600160601b03861681527f711cb21c80c465c7a5f641bdd3d984b4de7c3c762cec7a8d5b2e6252fdef5c189250602001905060405180910390a15050600160005550565b60408051606081018252600080825260208201819052918101919091526118a9826001600160601b0316612708565b62ffffff16604084018190526001600160a01b0391821660208501819052929091168084526000926118da92612b4e565b6040805160a0810182526001600160a01b038a811680835284821660208401528284018b9052606083018a90529088166080830152915163e70b9e2760e01b815260048101929092523060248301529192508891839160009073e34139463ba50bd61336e0c446bd8c0867c6fe659063e70b9e279060440160206040518083038186803b15801561196a57600080fd5b505afa15801561197e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119a2919061530f565b604051637aa4d5a160e11b815290915073e34139463ba50bd61336e0c446bd8c0867c6fe659063f549ab42906119de9085908b906004016159f8565b600060405180830381600087803b1580156119f857600080fd5b505af1158015611a0c573d6000803e3d6000fd5b505060405163e70b9e2760e01b81526001600160a01b03871660048201523060248201526000925073e34139463ba50bd61336e0c446bd8c0867c6fe65915063e70b9e279060440160206040518083038186803b158015611a6c57600080fd5b505afa158015611a80573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aa4919061530f565b9050611ab08282615da9565b600460008a6001600160601b0316815260200190815260200160002060008e6001600160a01b03166001600160a01b031681526020019081526020016000206000828254611afe9190615b6f565b9091555050505050505050505050505050565b604080516101208101825260008082526020820181905291810182905260608181018390526080820183905260a0820183905260c0820183905260e08201839052610100820183905282918190611b70886001600160601b0316610db9565b61010089015260e088015260c087015260a08601526001600160801b0316608085015250506001600160a01b0316808252611baa906136f7565b6000611bce82600001518360a001518460e001518560c00151866101000151612545565b90508015611bee5760405162461bcd60e51b81526004016103859061591b565b60005b8451811015611f2a5782516001600160a01b031660009081526008602090815260409182902080548351818402810184019094528084529091830182828015611c6357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611c45575b50505050509450845167ffffffffffffffff811115611c8457611c84615edd565b604051908082528060200260200182016040528015611cad578160200160208202803683370190505b509350611cdf8a6001600160601b0316868381518110611ccf57611ccf615ec7565b6020026020010151600019612e8d565b848281518110611cf157611cf1615ec7565b6020026020010181815250506000848281518110611d1157611d11615ec7565b60200260200101511115611f18576000858281518110611d3357611d33615ec7565b60200260200101519050611d763330878581518110611d5457611d54615ec7565b6020026020010151846001600160a01b03166127a4909392919063ffffffff16565b806001600160a01b031663095ea7b3734ee6ecad1c2dae9f525404de8555724e3c35d07b878581518110611dac57611dac615ec7565b60200260200101516040518363ffffffff1660e01b8152600401611de59291906001600160a01b03929092168252602082015260400190565b602060405180830381600087803b158015611dff57600080fd5b505af1158015611e13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e3791906152f4565b50734ee6ecad1c2dae9f525404de8555724e3c35d07b6001600160a01b03166335ed8ab8878481518110611e6d57611e6d615ec7565b6020026020010151878581518110611e8757611e87615ec7565b60200260200101516040518363ffffffff1660e01b8152600401611ec09291906001600160a01b03929092168252602082015260400190565b608060405180830381600087803b158015611eda57600080fd5b505af1158015611eee573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f1291906154a0565b50505050505b80611f2281615e2c565b915050611bf1565b50611f468983608001516001600160801b03168a8a60016121a8565b9096509450600080611f598b60016129ee565b90925090507fb0f09b3277a0e0f9c4edba2fcc3626129ed878e9d7c30b27589bdd484c9221208b611f8a848b615b6f565b611f94848b615b6f565b8989604051611fa7959493929190615aa3565b60405180910390a15050505093509350935093565b6001600160a01b0382166000908152600260209081526040808320848452909152808220805460ff191660011790555163133f757160e31b8152600481018390528190819081908190600080516020615f45833981519152906399fbab88906024016101806040518083038186803b15801561203757600080fd5b505afa15801561204b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061206f919061550e565b5050505050965096509650965096505050600061208d868686612b4e565b6001600160a01b03811660009081526001602052604090205490915060ff166120ef5760405162461bcd60e51b81526020600482015260146024820152731391950b5c1bdbdb0b5b9bdd0b595b98589b195960621b6044820152606401610385565b6001600160a01b0381166000908152600560205260409020546121128484615cf3565b62ffffff161161215c5760405162461bcd60e51b81526020600482015260156024820152746c6573732d7469636b732d646966666572656e636560581b6044820152606401610385565b604080516001600160a01b038a168152602081018990527fe0fbaffdb15cdfab5c685e516e471beb5234983e0f91c3330c780901fca28c82910160405180910390a15050505050505050565b6000806121c1876001600160601b03168760008061371c565b90925090508482116121e55760405162461bcd60e51b815260040161038590615952565b8381116122045760405162461bcd60e51b815260040161038590615952565b60008084156122a95760405163133f757160e31b81526001600160601b038a166004820152600080516020615f45833981519152906399fbab88906024016101806040518083038186803b15801561225b57600080fd5b505afa15801561226f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612293919061550e565b50979b5095995061230c98505050505050505050565b60008060008060006122c38e6001600160601b0316610db9565b959d50939b50949950975092955093509150600090506122e68686858786612545565b9050806123055760405162461bcd60e51b81526004016103859061591b565b5050505050505b612327896001600160601b03166001600160801b03806137ff565b505050509550959350505050565b6000808981806001600160a01b03831663514ea4bf612363600080516020615f458339815191528f8f6138d4565b6040518263ffffffff1660e01b815260040161238191815260200190565b60a06040518083038186803b15801561239957600080fd5b505afa1580156123ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d1919061535f565b505092509250506123fb89836123e79190615da9565b8b6001600160801b0316600160801b613930565b6124059088615b44565b96506124146123e78983615da9565b61241e9087615b44565b6001600160801b039788169e97169c50959a5050505050505050505050565b604051637b3c5da760e01b81526001600160a01b0383166004820152600090734ee6ecad1c2dae9f525404de8555724e3c35d07b90637b3c5da790602401604080518083038186803b15801561249257600080fd5b505afa1580156124a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124ca919061547c565b9150600090506124e6826114d385670de0b6b3a7640000613495565b60008681526006602090815260408083206001600160a01b03891684529091529020549091506125168183612c11565b60009687526006602090815260408089206001600160a01b039098168952969052949095209390935550505050565b6001600160a01b03851660009081526009602090815260408083208151808301909252546001600160801b038082168352600160801b909104169181018290529061259990612710906114d3908990613495565b85111580156125c5575080516125c190612710906114d39087906001600160801b0316613495565b8311155b979650505050505050565b6040516001600160a01b03831660248201526044810182905261263390849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152613ab5565b505050565b600080600061264684612708565b9250925092506000612659848484612b4e565b6040805160a0810182526001600160a01b03808d16825280841660208301528183018c9052606082018b905289166080820152905163f2d2909b60e01b8152919250829173e34139463ba50bd61336e0c446bd8c0867c6fe659063f2d2909b906126c99084908b906004016159dd565b600060405180830381600087803b1580156126e357600080fd5b505af11580156126f7573d6000803e3d6000fd5b505050505050505050505050505050565b60405163133f757160e31b81526004810182905260009081908190600080516020615f45833981519152906399fbab88906024016101806040518083038186803b15801561275557600080fd5b505afa158015612769573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061278d919061550e565b50979e969d50949b50949950505050505050505050565b6040516001600160a01b03808516602483015283166044820152606481018290526127dc9085906323b872dd60e01b906084016125fc565b50505050565b80158061286b5750604051636eb1769f60e11b81523060048201526001600160a01b03838116602483015284169063dd62ed3e9060440160206040518083038186803b15801561283157600080fd5b505afa158015612845573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612869919061530f565b155b6128d65760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527520746f206e6f6e2d7a65726f20616c6c6f77616e636560501b6064820152608401610385565b6040516001600160a01b03831660248201526044810182905261263390849063095ea7b360e01b906064016125fc565b6040805160c08101825287815260208101878152818301878152606083018781526080840187815260a08501878152955163219f5d1760e01b8152855160048201529351602485015291516044840152516064830152516084820152915160a483015260009182918291600080516020615f458339815191529063219f5d179060c401606060405180830381600087803b1580156129a357600080fd5b505af11580156129b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129db9190615328565b919c909b50909950975050505050505050565b600080612a0c846001600160601b03166001600160801b03806137ff565b90925090506000808415612ab65760405163133f757160e31b81526001600160601b0387166004820152600080516020615f45833981519152906399fbab88906024016101806040518083038186803b158015612a6857600080fd5b505afa158015612a7c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612aa0919061550e565b50979b50959950612b1998505050505050505050565b6000806000806000612ad08b6001600160601b0316610db9565b959d50939b5094995097509295509350915060009050612af38686858786612545565b905080612b125760405162461bcd60e51b81526004016103859061591b565b5050505050505b8181612b2f6001600160a01b03831633886125d0565b612b436001600160a01b03821633876125d0565b505050509250929050565b6000612c09600080516020615f458339815191526001600160a01b031663c45a01556040518163ffffffff1660e01b815260040160206040518083038186803b158015612b9a57600080fd5b505afa158015612bae573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612bd29190615072565b6040518060600160405280876001600160a01b03168152602001866001600160a01b031681526020018562ffffff16815250613b87565b949350505050565b6000612c1d8284615b6f565b90505b92915050565b600080600080612c576040518060800160405280600081526020016000815260200160008152602001600081525090565b86600081518110612c6a57612c6a615ec7565b6020026020010151891115612ca65786600081518110612c8c57612c8c615ec7565b602002602001015189612c9f9190615da9565b8152612cd2565b8887600081518110612cba57612cba615ec7565b6020026020010151612ccc9190615da9565b60408201525b86600181518110612ce557612ce5615ec7565b6020026020010151881115612d245786600181518110612d0757612d07615ec7565b602002602001015188612d1a9190615da9565b6020820152612d50565b8787600181518110612d3857612d38615ec7565b6020026020010151612d4a9190615da9565b60608201525b6001600160a01b038616600090815260086020908152604080832080548251818502810185019093528083529192909190830182828015612dba57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612d9c575b505050505090506000612dcc82613c70565b90506000612dd983613d1b565b9050612e5b8c8c8660000151876020015186600081518110612dfd57612dfd615ec7565b602002602001015187600181518110612e1857612e18615ec7565b602002602001015187600081518110612e3357612e33615ec7565b602002602001015188600181518110612e4e57612e4e615ec7565b6020026020010151613e30565b8098508199505050612e788a856040015186606001518585613eb7565b989d979c509a50969850949650505050505050565b604051637b3c5da760e01b81526001600160a01b03831660048201526000908190734ee6ecad1c2dae9f525404de8555724e3c35d07b90637b3c5da790602401604080518083038186803b158015612ee457600080fd5b505afa158015612ef8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f1c919061547c565b60008781526006602090815260408083206001600160a01b038a16845290915290205490925090506000198414612f9d576000612f65836114d387670de0b6b3a7640000613495565b9050612f71828261403b565b60008881526006602090815260408083206001600160a01b038b16845290915290205550839250612fd9565b612fb3670de0b6b3a76400006114d38385613495565b60008781526006602090815260408083206001600160a01b038a16845290915281205592505b50509392505050565b60008060008360020b12612ff9578260020b613006565b8260020b61300690615e7e565b9050613015620d89e719615e5b565b62ffffff1681111561304d5760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610385565b60006001821661306157600160801b613073565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff16905060028216156130b25760806130ad826ffff97272373d413259a46990580e213a615cd4565b901c90505b60048216156130dc5760806130d7826ffff2e50f5f656932ef12357cf3c7fdcc615cd4565b901c90505b6008821615613106576080613101826fffe5caca7e10e4e61c3624eaa0941cd0615cd4565b901c90505b601082161561313057608061312b826fffcb9843d60f6159c9db58835c926644615cd4565b901c90505b602082161561315a576080613155826fff973b41fa98c081472e6896dfb254c0615cd4565b901c90505b604082161561318457608061317f826fff2ea16466c96a3843ec78b326b52861615cd4565b901c90505b60808216156131ae5760806131a9826ffe5dee046a99a2a811c461f1969c3053615cd4565b901c90505b6101008216156131d95760806131d4826ffcbe86c7900a88aedcffc83b479aa3a4615cd4565b901c90505b6102008216156132045760806131ff826ff987a7253ac413176f2b074cf7815e54615cd4565b901c90505b61040082161561322f57608061322a826ff3392b0822b70005940c7a398e4b70f3615cd4565b901c90505b61080082161561325a576080613255826fe7159475a2c29b7443b29c7fa6e889d9615cd4565b901c90505b611000821615613285576080613280826fd097f3bdfd2022b8845ad8f792aa5825615cd4565b901c90505b6120008216156132b05760806132ab826fa9f746462d870fdf8a65dc1f90e061e5615cd4565b901c90505b6140008216156132db5760806132d6826f70d869a156d2a1b890bb3df62baf32f7615cd4565b901c90505b618000821615613306576080613301826f31be135f97d08fd981231505542fcfa6615cd4565b901c90505b6201000082161561333257608061332d826f09aa508b5b7a84e1c677de54f3e99bc9615cd4565b901c90505b6202000082161561335d576080613358826e5d6af8dedb81196699c329225ee604615cd4565b901c90505b62040000821615613387576080613382826d2216e584f5fa1ea926041bedfe98615cd4565b901c90505b620800008216156133af5760806133aa826b048a170391f7dc42444e8fa2615cd4565b901c90505b60008460020b13156133ca576133c781600019615bc5565b90505b6133d964010000000082615e47565b156133e55760016133e8565b60005b612c099060ff16602083901c615b6f565b600080836001600160a01b0316856001600160a01b0316111561341a579293925b846001600160a01b0316866001600160a01b0316116134455761343e858585614047565b915061348c565b836001600160a01b0316866001600160a01b0316101561347e5761346a868585614047565b91506134778587856140ba565b905061348c565b6134898585856140ba565b90505b94509492505050565b6000612c1d8284615cd4565b6000612c1d8284615bc5565b604051632f2d783d60e01b81526001600160a01b038085166004830152831660248201526044810182905273e34139463ba50bd61336e0c446bd8c0867c6fe6590632f2d783d90606401602060405180830381600087803b15801561351157600080fd5b505af1158015613525573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127dc919061530f565b60405163133f757160e31b815260048101829052600090819081908190600080516020615f45833981519152906399fbab88906024016101806040518083038186803b15801561359857600080fd5b505afa1580156135ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135d0919061550e565b50505050505050945094509450505060006135ec848484612b4e565b6001600160a01b03811660009081526008602090815260408083208054825181850281018501909352808352949550929390929183018282801561365957602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161363b575b5050505050905060005b81518110156136ec5760066000898152602001908152602001600020600083838151811061369357613693615ec7565b60200260200101516001600160a01b03166001600160a01b03168152602001908152602001600020546000146136ca5760016136cd565b60005b965086156136da576136ec565b806136e481615e2c565b915050613663565b505050505050919050565b80600061370382614104565b90506000613710826146e6565b90506127dc8382614724565b6040805160a0810182528581526001600160801b03858116602083019081528284018681526060840186815242608086019081529551630624e65f60e11b815285516004820152925190931660248301525160448201529051606482015291516084830152600091829190600080516020615f4583398151915290630c49ccbe9060a4016040805180830381600087803b1580156137b957600080fd5b505af11580156137cd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137f1919061547c565b909890975095505050505050565b6040805160808101825284815233602082019081526001600160801b0385811683850190815285821660608501908152945163fc6f786560e01b81528451600482015292516001600160a01b03166024840152518116604483015292519092166064830152600091829190600080516020615f458339815191529063fc6f7865906084016040805180830381600087803b15801561389c57600080fd5b505af11580156138b0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610805919061547c565b6040516bffffffffffffffffffffffff19606085901b166020820152600283810b60e890811b60348401529083900b901b6037820152600090603a016040516020818303038152906040528051906020012090505b9392505050565b60008080600019858709858702925082811083820303915050806000141561396a576000841161395f57600080fd5b508290049050613929565b80841161397657600080fd5b60008486880980840393811190920391905060008561399781600019615da9565b6139a2906001615b6f565b169586900495938490049360008190030460010190506139c28184615cd4565b9093179260006139d3876003615cd4565b60021890506139e28188615cd4565b6139ed906002615da9565b6139f79082615cd4565b9050613a038188615cd4565b613a0e906002615da9565b613a189082615cd4565b9050613a248188615cd4565b613a2f906002615da9565b613a399082615cd4565b9050613a458188615cd4565b613a50906002615da9565b613a5a9082615cd4565b9050613a668188615cd4565b613a71906002615da9565b613a7b9082615cd4565b9050613a878188615cd4565b613a92906002615da9565b613a9c9082615cd4565b9050613aa88186615cd4565b9998505050505050505050565b6000613b0a826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166148f69092919063ffffffff16565b8051909150156126335780806020019051810190613b2891906152f4565b6126335760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610385565b600081602001516001600160a01b031682600001516001600160a01b031610613baf57600080fd5b815160208084015160408086015181516001600160a01b0395861681860152949092168482015262ffffff90911660608085019190915281518085038201815260808501909252815191909201206001600160f81b031960a08401529085901b6bffffffffffffffffffffffff191660a183015260b58201527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460d582015260f50160408051601f1981840301815291905280516020909101209392505050565b6060815167ffffffffffffffff811115613c8c57613c8c615edd565b604051908082528060200260200182016040528015613cb5578160200160208202803683370190505b50905060005b8251811015613d1557613ce6838281518110613cd957613cd9615ec7565b6020026020010151614905565b828281518110613cf857613cf8615ec7565b602090810291909101015280613d0d81615e2c565b915050613cbb565b50919050565b80516060908067ffffffffffffffff811115613d3957613d39615edd565b604051908082528060200260200182016040528015613d62578160200160208202803683370190505b50915060005b81811015613e2957838181518110613d8257613d82615ec7565b60200260200101516001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015613dc257600080fd5b505afa158015613dd6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613dfa919061530f565b838281518110613e0c57613e0c615ec7565b602090810291909101015280613e2181615e2c565b915050613d68565b5050919050565b60008080613e4c613e4286600a615c1c565b6114d38d8a613495565b90506000613e5e613e4286600a615c1c565b90506000613e7a613e7088600a615c1c565b6114d38d8c613495565b90506000613e8c613e7088600a615c1c565b9050613e988484612c11565b9550613ea48282612c11565b9450505050509850989650505050505050565b60008060005b8751811015614030576000613f36858381518110613edd57613edd615ec7565b6020026020010151600a613ef19190615c1c565b6114d3888581518110613f0657613f06615ec7565b60200260200101518c8681518110613f2057613f20615ec7565b602002602001015161349590919063ffffffff16565b9050613f428482612c11565b935081613faf57613fa8613fa1868481518110613f6157613f61615ec7565b6020026020010151600a613f759190615c1c565b6114d3898681518110613f8a57613f8a615ec7565b60200260200101518c61349590919063ffffffff16565b8490612c11565b925061401d565b816001141561401057613fa8613fa1868481518110613fd057613fd0615ec7565b6020026020010151600a613fe49190615c1c565b6114d3898681518110613ff957613ff9615ec7565b60200260200101518b61349590919063ffffffff16565b61401a8382612c11565b92505b508061402881615e2c565b915050613ebd565b509550959350505050565b6000612c1d8284615da9565b6000826001600160a01b0316846001600160a01b03161115614067579192915b6001600160a01b0384166140b06fffffffffffffffffffffffffffffffff60601b606085901b166140988787615d81565b6001600160a01b0316866001600160a01b0316613930565b612c099190615bc5565b6000826001600160a01b0316846001600160a01b031611156140da579192915b612c096001600160801b0383166140f18686615d81565b6001600160a01b0316600160601b613930565b6000816001600160a01b0316633850c7bd6040518163ffffffff1660e01b815260040160e06040518083038186803b15801561413f57600080fd5b505afa158015614153573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061417791906153b3565b505050506001600160a01b0385166000908152600b60209081526040808320815161014081018352905462ffffff808216835263010000008204811694830194909452660100000000000081048416828401526901000000000000000000810484166060830152600160601b810484166080830152600160781b8104841660a0830152600160901b8104841660c0830152600160a81b8104841660e080840191909152600160c01b82048516610100840152600160d81b909104909316610120820152815160068082529381019092529496509394509092915081602001602082028036833701905050905060008160008151811061427857614278615ec7565b602002602001019063ffffffff16908163ffffffff1681525050816020015162ffffff16816001815181106142af576142af615ec7565b602002602001019063ffffffff16908163ffffffff1681525050816060015162ffffff16816002815181106142e6576142e6615ec7565b602002602001019063ffffffff16908163ffffffff16815250508160a0015162ffffff168160038151811061431d5761431d615ec7565b602002602001019063ffffffff16908163ffffffff16815250508160e0015162ffffff168160048151811061435457614354615ec7565b602002602001019063ffffffff16908163ffffffff168152505081610120015162ffffff168160058151811061438c5761438c615ec7565b602002602001019063ffffffff16908163ffffffff168152505060006143b28583614a20565b60408051600580825260c08201909252919250600091906020820160a08036833701905050905060005b600581101561449c578281815181106143f7576143f7615ec7565b602002602001015160060b8660020b126144385782818151811061441d5761441d615ec7565b60200260200101518660020b6144339190615d3b565b614460565b8560020b83828151811061444e5761444e615ec7565b60200260200101516144609190615d3b565b82828151811061447257614472615ec7565b66ffffffffffffff909216602092830291909101909101528061449481615e2c565b9150506143dc565b50836000015162ffffff16816000815181106144ba576144ba615ec7565b602002602001015166ffffffffffffff16106145105760405162461bcd60e51b81526020600482015260156024820152746578636573732d7469636b2d736c6970706167653160581b6044820152606401610385565b836040015162ffffff168160018151811061452d5761452d615ec7565b602002602001015166ffffffffffffff16106145835760405162461bcd60e51b815260206004820152601560248201527432bc31b2b9b996ba34b1b596b9b634b83830b3b29960591b6044820152606401610385565b836080015162ffffff16816002815181106145a0576145a0615ec7565b602002602001015166ffffffffffffff16106145f65760405162461bcd60e51b81526020600482015260156024820152746578636573732d7469636b2d736c6970706167653360581b6044820152606401610385565b8360c0015162ffffff168160038151811061461357614613615ec7565b602002602001015166ffffffffffffff16106146695760405162461bcd60e51b8152602060048201526015602482015274195e18d95cdccb5d1a58dacb5cdb1a5c1c1859d94d605a1b6044820152606401610385565b83610100015162ffffff168160048151811061468757614687615ec7565b602002602001015166ffffffffffffff16106146dd5760405162461bcd60e51b81526020600482015260156024820152746578636573732d7469636b2d736c6970706167653560581b6044820152606401610385565b50505050919050565b6000806146f283612fe2565b6001600160a01b0316905060c061471b670de0b6b3a76400006147158480613495565b90613495565b901c9392505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b815260040160206040518083038186803b15801561475f57600080fd5b505afa158015614773573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147979190615072565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b815260040160206040518083038186803b1580156147d457600080fd5b505afa1580156147e8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061480c9190615072565b9050600061481a8383614bd6565b6001600160a01b0386166000908152600a60205260408120549192509061484a90612710906114d3908890613495565b905081614857868361403b565b1061489d5760405162461bcd60e51b815260206004820152601660248201527570726963652d6f7574736964652d6f662d72616e676560501b6044820152606401610385565b816148a88683612c11565b116148ee5760405162461bcd60e51b815260206004820152601660248201527570726963652d6f7574736964652d6f662d72616e676560501b6044820152606401610385565b505050505050565b6060612c098484600085614d0d565b6001600160a01b038082166000908152600c6020908152604080832054815163313ce56760e01b8152915193941692839263313ce5679260048082019391829003018186803b15801561495757600080fd5b505afa15801561496b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061498f91906154d6565b61499a906012615ddd565b6149a590600a615c28565b816001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b1580156149de57600080fd5b505afa1580156149f2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614a16919061530f565b6139299190615cd4565b60606000836001600160a01b031663883bdbfd846040518263ffffffff1660e01b8152600401614a50919061589e565b60006040518083038186803b158015614a6857600080fd5b505afa158015614a7c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052614aa49190810190615222565b50905060018351614ab59190615da9565b67ffffffffffffffff811115614acd57614acd615edd565b604051908082528060200260200182016040528015614af6578160200160208202803683370190505b50915060015b835181101561150d5783600081518110614b1857614b18615ec7565b6020026020010151848281518110614b3257614b32615ec7565b6020026020010151614b449190615dc0565b63ffffffff1682600081518110614b5d57614b5d615ec7565b6020026020010151838381518110614b7757614b77615ec7565b6020026020010151614b899190615d3b565b614b939190615b87565b83614b9f600184615da9565b81518110614baf57614baf615ec7565b602002602001019060060b908160060b815250508080614bce90615e2c565b915050614afc565b600080614be284614905565b90506000614bef84614905565b90506000856001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015614c2c57600080fd5b505afa158015614c40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614c64919061530f565b90506000856001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b158015614ca157600080fd5b505afa158015614cb5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190614cd9919061530f565b90506125c5614ce983600a615c1c565b6114d3614cf784600a615c1c565b614715876114d38a670de0b6b3a7640000613495565b606082471015614d6e5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610385565b843b614dbc5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610385565b600080866001600160a01b03168587604051614dd8919061586f565b60006040518083038185875af1925050503d8060008114614e15576040519150601f19603f3d011682016040523d82523d6000602084013e614e1a565b606091505b50915091506125c582828660608315614e34575081613929565b825115614e445782518084602001fd5b8160405162461bcd60e51b815260040161038591906158e8565b604051806101600160405280600062ffffff168152602001600060020b8152602001600060020b8152602001600081526020016000815260200160006001600160801b0316815260200160006001600160801b03168152602001600081526020016000815260200160008152602001600081525090565b600082601f830112614ee657600080fd5b81356020614efb614ef683615b20565b615aef565b80838252828201915082860187848660051b8901011115614f1b57600080fd5b60005b85811015614f43578135614f3181615ef3565b84529284019290840190600101614f1e565b5090979650505050505050565b600082601f830112614f6157600080fd5b81516020614f71614ef683615b20565b80838252828201915082860187848660051b8901011115614f9157600080fd5b60005b85811015614f43578151614fa781615ef3565b84529284019290840190600101614f94565b600082601f830112614fca57600080fd5b81356020614fda614ef683615b20565b80838252828201915082860187848660051b8901011115614ffa57600080fd5b60005b85811015614f4357813584529284019290840190600101614ffd565b8051801515811461502957600080fd5b919050565b805161502981615f0b565b805161502981615f1a565b805161ffff8116811461502957600080fd5b805160ff8116811461502957600080fd5b803561502981615f2f565b60006020828403121561508457600080fd5b815161392981615ef3565b6000806000806000608086880312156150a757600080fd5b85356150b281615ef3565b945060208601356150c281615ef3565b935060408601359250606086013567ffffffffffffffff808211156150e657600080fd5b818801915088601f8301126150fa57600080fd5b81358181111561510957600080fd5b89602082850101111561511b57600080fd5b9699959850939650602001949392505050565b6000806000806080858703121561514457600080fd5b843561514f81615ef3565b9350602085013561515f81615f0b565b9250604085013561516f81615f0b565b9150606085013561517f81615f1a565b939692955090935050565b600080600080600060a086880312156151a257600080fd5b85356151ad81615ef3565b9450602086013593506040860135925060608601356151cb81615ef3565b915060808601356151db81615f2f565b809150509295509295909350565b600080604083850312156151fc57600080fd5b823561520781615ef3565b9150602083013561521781615f2f565b809150509250929050565b6000806040838503121561523557600080fd5b825167ffffffffffffffff8082111561524d57600080fd5b818501915085601f83011261526157600080fd5b81516020615271614ef683615b20565b8083825282820191508286018a848660051b890101111561529157600080fd5b600096505b848710156152c35780518060060b81146152af57600080fd5b835260019690960195918301918301615296565b50918801519196509093505050808211156152dd57600080fd5b506152ea85828601614f50565b9150509250929050565b60006020828403121561530657600080fd5b612c1d82615019565b60006020828403121561532157600080fd5b5051919050565b60008060006060848603121561533d57600080fd5b835161534881615f1a565b602085015160409095015190969495509392505050565b600080600080600060a0868803121561537757600080fd5b855161538281615f1a565b80955050602086015193506040860151925060608601516153a281615f1a565b60808701519092506151db81615f1a565b600080600080600080600060e0888a0312156153ce57600080fd5b87516153d981615ef3565b60208901519097506153ea81615f0b565b95506153f860408901615044565b945061540660608901615044565b935061541460808901615044565b925061542260a08901615056565b915061543060c08901615019565b905092959891949750929550565b60006020828403121561545057600080fd5b5035919050565b6000806040838503121561546a57600080fd5b82359150602083013561521781615ef3565b6000806040838503121561548f57600080fd5b505080516020909101519092909150565b600080600080608085870312156154b657600080fd5b505082516020840151604085015160609095015191969095509092509050565b6000602082840312156154e857600080fd5b612c1d82615056565b60006020828403121561550357600080fd5b813561392981615f2f565b6000806000806000806000806000806000806101808d8f03121561553157600080fd5b8c5161553c81615f2f565b60208e0151909c5061554d81615ef3565b60408e0151909b5061555e81615ef3565b60608e0151909a5061556f81615ef3565b60808e015190995062ffffff8116811461558857600080fd5b975061559660a08e0161502e565b96506155a460c08e0161502e565b95506155b260e08e01615039565b94506101008d015193506101208d015192506155d16101408e01615039565b91506155e06101608e01615039565b90509295989b509295989b509295989b565b60008060006060848603121561560757600080fd5b833561561281615f2f565b9250602084013561562281615ef3565b929592945050506040919091013590565b60008060006060848603121561564857600080fd5b833561565381615f2f565b95602085013595506040909401359392505050565b600080600080600080600060e0888a03121561568357600080fd5b61568c88615067565b96506020880135955060408801359450606088013567ffffffffffffffff808211156156b757600080fd5b6156c38b838c01614ed5565b955060808a01359150808211156156d957600080fd5b6156e58b838c01614fb9565b945060a08a01359150808211156156fb57600080fd5b6157078b838c01614fb9565b935060c08a013591508082111561571d57600080fd5b5061572a8a828b01614ed5565b91505092959891949750929550565b6000806000806080858703121561574f57600080fd5b843561575a81615f2f565b966020860135965060408601359560600135945092505050565b60008060008060008060c0878903121561578d57600080fd5b863561579881615f2f565b9860208801359850604088013597606081013597506080810135965060a00135945092505050565b600081518084526020808501945080840160005b838110156157f95781516001600160a01b0316875295820195908201906001016157d4565b509495945050505050565b600081518084526020808501945080840160005b838110156157f957815187529582019590820190600101615818565b80516001600160a01b039081168352602080830151821690840152604080830151908401526060808301519084015260809182015116910152565b60008251615881818460208701615e00565b9190910192915050565b602081526000612c1d6020830184615804565b6020808252825182820181905260009190848201906040850190845b818110156158dc57835163ffffffff16835292840192918401916001016158ba565b50909695505050505050565b6020815260008251806020840152615907816040850160208701615e00565b601f01601f19169190910160400192915050565b60208082526017908201527f706f736974696f6e2d77696c6c2d6c6971756964617465000000000000000000604082015260600190565b6020808252601490820152731b195cdccb5d1a185b8b5b5a5b8b585b5bdd5b9d60621b604082015260600190565b6020808252600c908201526b3737ba16b0b716b7bbb732b960a11b604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b60c081016159eb8285615834565b8260a08301529392505050565b60c08101615a068285615834565b6001600160601b03831660a08301529392505050565b6020815281516020820152602082015160408201526000604083015160806060840152615a4c60a08401826157c0565b90506060840151601f19848303016080850152615a698282615804565b95945050505050565b848152836020820152608060408201526000615a9160808301856157c0565b82810360608401526125c58185615804565b6001600160601b038616815284602082015283604082015260a060608201526000615ad160a08301856157c0565b8281036080840152615ae38185615804565b98975050505050505050565b604051601f8201601f1916810167ffffffffffffffff81118282101715615b1857615b18615edd565b604052919050565b600067ffffffffffffffff821115615b3a57615b3a615edd565b5060051b60200190565b60006001600160801b03808316818516808303821115615b6657615b66615e9b565b01949350505050565b60008219821115615b8257615b82615e9b565b500190565b60008160060b8360060b80615b9e57615b9e615eb1565b667fffffffffffff19821460001982141615615bbc57615bbc615e9b565b90059392505050565b600082615bd457615bd4615eb1565b500490565b600181815b80851115615c14578160001904821115615bfa57615bfa615e9b565b80851615615c0757918102915b93841c9390800290615bde565b509250929050565b6000612c1d8383615c33565b6000612c1d60ff8416835b600082615c4257506001612c20565b81615c4f57506000612c20565b8160018114615c655760028114615c6f57615c8b565b6001915050612c20565b60ff841115615c8057615c80615e9b565b50506001821b612c20565b5060208310610133831016604e8410600b8410161715615cae575081810a612c20565b615cb88383615bd9565b8060001904821115615ccc57615ccc615e9b565b029392505050565b6000816000190483118215151615615cee57615cee615e9b565b500290565b60008160020b8360020b6000811281627fffff1901831281151615615d1a57615d1a615e9b565b81627fffff018313811615615d3157615d31615e9b565b5090039392505050565b60008160060b8360060b6000811281667fffffffffffff1901831281151615615d6657615d66615e9b565b81667fffffffffffff018313811615615d3157615d31615e9b565b60006001600160a01b0383811690831681811015615da157615da1615e9b565b039392505050565b600082821015615dbb57615dbb615e9b565b500390565b600063ffffffff83811690831681811015615da157615da1615e9b565b600060ff821660ff841680821015615df757615df7615e9b565b90039392505050565b60005b83811015615e1b578181015183820152602001615e03565b838111156127dc5750506000910152565b6000600019821415615e4057615e40615e9b565b5060010190565b600082615e5657615e56615eb1565b500690565b60008160020b627fffff19811415615e7557615e75615e9b565b60000392915050565b6000600160ff1b821415615e9457615e94615e9b565b5060000390565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114615f0857600080fd5b50565b8060020b8114615f0857600080fd5b6001600160801b0381168114615f0857600080fd5b6001600160601b0381168114615f0857600080fdfe000000000000000000000000c36442b4a4522e871399cd717abdd847ab11fe88a26469706673582212204585b6b8e47c3af226c3a0fc24734dc4d97e506ae6413da0f8e249ec63af977364736f6c63430008060033

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