ETH Price: $2,817.48 (-4.49%)
 

Overview

ETH Balance

Scroll LogoScroll LogoScroll Logo0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Set Price Calcul...64360982024-06-10 9:21:16594 days ago1718011276IN
0xf893D551...A850AD979
0 ETH0.000011180.21
Set Core64360962024-06-10 9:21:10594 days ago1718011270IN
0xf893D551...A850AD979
0 ETH0.000011220.21
Initialize64360932024-06-10 9:21:01594 days ago1718011261IN
0xf893D551...A850AD979
0 ETH0.000011290.21

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Validator

Compiler Version
v0.6.12+commit.27d51765

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../interfaces/IPriceCalculator.sol";
import "../interfaces/IValidator.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/ICore.sol";
import "../interfaces/IBEP20.sol";
import "../library/Constant.sol";

contract Validator is IValidator, Ownable {
  using SafeMath for uint256;

  /* ========== CONSTANT VARIABLES ========== */

  IPriceCalculator public oracle;
  uint256 private constant labPriceCollateralCap = 75e15;
  uint256 private constant DUST = 1000;

  /* ========== STATE VARIABLES ========== */

  ICore public core;
  address private LAB;

  bool public initialized;

  /* ========== INITIALIZER ========== */

  constructor() public {}

  function initialize(address _lab) external onlyOwner {
    require(initialized == false, "already initialized");

    LAB = _lab;

    initialized = true;
  }

  /// @notice priceCalculator address 를 설정
  /// @dev ZERO ADDRESS 로 설정할 수 없음
  /// @param _priceCalculator priceCalculator contract address
  function setPriceCalculator(address _priceCalculator) public onlyOwner {
    require(_priceCalculator != address(0), "Validator: invalid priceCalculator address");
    oracle = IPriceCalculator(_priceCalculator);
  }

  /* ========== VIEWS ========== */

  /// @notice View collateral, supply, borrow value in USD of account
  /// @param account account address
  /// @return collateralInUSD Total collateral value in USD
  /// @return supplyInUSD Total supply value in USD
  /// @return borrowInUSD Total borrow value in USD
  function getAccountLiquidity(
    address account
  ) external view override returns (uint256 collateralInUSD, uint256 supplyInUSD, uint256 borrowInUSD) {
    collateralInUSD = 0;
    supplyInUSD = 0;
    borrowInUSD = 0;

    address[] memory assets = core.marketListOf(account);
    uint256[] memory prices = oracle.getUnderlyingPrices(assets);
    for (uint256 i = 0; i < assets.length; i++) {
      require(prices[i] != 0, "Validator: price error");
      uint256 decimals = _getDecimals(assets[i]);
      Constant.AccountSnapshot memory snapshot = ILToken(payable(assets[i])).accountSnapshot(account);

      uint256 priceCollateral;
      if (assets[i] == LAB && prices[i] > labPriceCollateralCap) {
        priceCollateral = labPriceCollateralCap;
      } else {
        priceCollateral = prices[i];
      }

      uint256 collateralFactor = core.marketInfoOf(payable(assets[i])).collateralFactor;
      uint256 collateralValuePerShareInUSD = snapshot.exchangeRate.mul(priceCollateral).mul(collateralFactor).div(1e36);

      collateralInUSD = collateralInUSD.add(
        snapshot.lTokenBalance.mul(10 ** (18 - decimals)).mul(collateralValuePerShareInUSD).div(1e18)
      );
      supplyInUSD = supplyInUSD.add(
        snapshot.lTokenBalance.mul(snapshot.exchangeRate).mul(10 ** (18 - decimals)).mul(prices[i]).div(1e36)
      );
      borrowInUSD = borrowInUSD.add(snapshot.borrowBalance.mul(10 ** (18 - decimals)).mul(prices[i]).div(1e18));
    }
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function setCore(address _core) external onlyOwner {
    require(_core != address(0), "Validator: invalid core address");
    require(address(core) == address(0), "Validator: core already set");
    core = ICore(_core);
  }

  /* ========== ALLOWED FUNCTIONS ========== */

  function redeemAllowed(address lToken, address redeemer, uint256 redeemAmount) external override returns (bool) {
    (, uint256 shortfall) = _getAccountLiquidityInternal(redeemer, lToken, redeemAmount, 0);
    return shortfall == 0;
  }

  function borrowAllowed(address lToken, address borrower, uint256 borrowAmount) external override returns (bool) {
    require(borrowAmount > DUST, "Validator: too small borrow amount");
    require(core.checkMembership(borrower, address(lToken)), "Validator: enterMarket required");
    require(oracle.getUnderlyingPrice(address(lToken)) > 0, "Validator: Underlying price error");

    // Borrow cap of 0 corresponds to unlimited borrowing
    uint256 borrowCap = core.marketInfoOf(lToken).borrowCap;
    if (borrowCap != 0) {
      uint256 totalBorrows = ILToken(payable(lToken)).accruedTotalBorrow();
      uint256 nextTotalBorrows = totalBorrows.add(borrowAmount);
      require(nextTotalBorrows < borrowCap, "Validator: market borrow cap reached");
    }

    (, uint256 shortfall) = _getAccountLiquidityInternal(borrower, lToken, 0, borrowAmount);
    return shortfall == 0;
  }

  function liquidateAllowed(
    address lToken,
    address borrower,
    uint256 liquidateAmount,
    uint256 closeFactor
  ) external override returns (bool) {
    // The borrower must have shortfall in order to be liquidate
    (, uint256 shortfall) = _getAccountLiquidityInternal(borrower, address(0), 0, 0);
    require(shortfall != 0, "Validator: Insufficient shortfall");

    // The liquidator may not repay more than what is allowed by the closeFactor
    uint256 borrowBalance = ILToken(payable(lToken)).accruedBorrowBalanceOf(borrower);
    uint256 maxClose = closeFactor.mul(borrowBalance).div(1e18);
    return liquidateAmount <= maxClose;
  }

  function lTokenAmountToSeize(
    address lTokenBorrowed,
    address lTokenCollateral,
    uint256 amount
  ) external override returns (uint256 seizeLAmount, uint256 rebateLAmount, uint256 liquidatorLAmount) {
    require(
      oracle.getUnderlyingPrice(lTokenBorrowed) != 0 && oracle.getUnderlyingPrice(lTokenCollateral) != 0,
      "Validator: price error"
    );

    uint256 exchangeRate = ILToken(payable(lTokenCollateral)).accruedExchangeRate();
    require(exchangeRate != 0, "Validator: exchangeRate of lTokenCollateral is zero");

    uint256 borrowedDecimals = _getDecimals(lTokenBorrowed);
    uint256 collateralDecimals = _getDecimals(lTokenCollateral);

    uint256 seizeLTokenAmountBase = amount
      .mul(10 ** (18 - borrowedDecimals))
      .mul(core.liquidationIncentive())
      .mul(oracle.getUnderlyingPrice(lTokenBorrowed))
      .div(oracle.getUnderlyingPrice(lTokenCollateral).mul(exchangeRate));

    seizeLAmount = seizeLTokenAmountBase.div(10 ** (18 - collateralDecimals));
    liquidatorLAmount = seizeLAmount;
    rebateLAmount = 0;
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _getAccountLiquidityInternal(
    address account,
    address lToken,
    uint256 redeemAmount,
    uint256 borrowAmount
  ) private returns (uint256 liquidity, uint256 shortfall) {
    uint256 accCollateralValueInUSD;
    uint256 accBorrowValueInUSD;

    address[] memory assets = core.marketListOf(account);
    uint256[] memory prices = oracle.getUnderlyingPrices(assets);
    for (uint256 i = 0; i < assets.length; i++) {
      uint256 decimals = _getDecimals(assets[i]);
      require(prices[i] != 0, "Validator: price error");
      Constant.AccountSnapshot memory snapshot = ILToken(payable(assets[i])).accruedAccountSnapshot(account);

      uint256 collateralValuePerShareInUSD;
      if (assets[i] == LAB && prices[i] > labPriceCollateralCap) {
        collateralValuePerShareInUSD = snapshot
          .exchangeRate
          .mul(labPriceCollateralCap)
          .mul(core.marketInfoOf(payable(assets[i])).collateralFactor)
          .div(1e36);
      } else {
        collateralValuePerShareInUSD = snapshot
          .exchangeRate
          .mul(prices[i])
          .mul(core.marketInfoOf(payable(assets[i])).collateralFactor)
          .div(1e36);
      }

      accCollateralValueInUSD = accCollateralValueInUSD.add(
        snapshot.lTokenBalance.mul(10 ** (18 - decimals)).mul(collateralValuePerShareInUSD).div(1e18)
      );
      accBorrowValueInUSD = accBorrowValueInUSD.add(
        snapshot.borrowBalance.mul(10 ** (18 - decimals)).mul(prices[i]).div(1e18)
      );

      if (assets[i] == lToken) {
        accBorrowValueInUSD = accBorrowValueInUSD.add(
          _getAmountForAdditionalBorrowValue(
            redeemAmount,
            borrowAmount,
            collateralValuePerShareInUSD,
            prices[i],
            decimals
          )
        );
      }
    }

    liquidity = accCollateralValueInUSD > accBorrowValueInUSD ? accCollateralValueInUSD.sub(accBorrowValueInUSD) : 0;
    shortfall = accCollateralValueInUSD > accBorrowValueInUSD ? 0 : accBorrowValueInUSD.sub(accCollateralValueInUSD);
  }

  function _getAmountForAdditionalBorrowValue(
    uint256 redeemAmount,
    uint256 borrowAmount,
    uint256 collateralValuePerShareInUSD,
    uint256 price,
    uint256 decimals
  ) internal pure returns (uint256 additionalBorrowValueInUSD) {
    additionalBorrowValueInUSD = redeemAmount.mul(10 ** (18 - decimals)).mul(collateralValuePerShareInUSD).div(1e18);
    additionalBorrowValueInUSD = additionalBorrowValueInUSD.add(
      borrowAmount.mul(10 ** (18 - decimals)).mul(price).div(1e18)
    );
  }

  function _getDecimals(address lToken) internal view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18; // ETH
    } else {
      decimals = IBEP20(underlying).decimals();
    }
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";

import "./CoreAdmin.sol";

import "./interfaces/ILToken.sol";
import "./interfaces/IValidator.sol";
import "./interfaces/IPriceCalculator.sol";

contract Core is CoreAdmin {
  using SafeMath for uint256;

  /* ========== CONSTANT VARIABLES ========== */

  address internal constant ETH = 0x0000000000000000000000000000000000000000;

  /* ========== STATE VARIABLES ========== */

  mapping(address => address[]) public marketListOfUsers; // (account => lTokenAddress[])
  mapping(address => mapping(address => bool)) public usersOfMarket; // (lTokenAddress => (account => joined))

  // initializer
  bool public initialized;

  /* ========== INITIALIZER ========== */

  constructor() public {}

  function initialize(address _priceCalculator) external onlyOwner {
    require(initialized == false, "already initialized");

    __Core_init();
    priceCalculator = IPriceCalculator(_priceCalculator);

    initialized = true;
  }

  /* ========== MODIFIERS ========== */

  modifier onlyMemberOfMarket(address lToken) {
    require(usersOfMarket[lToken][msg.sender], "Core: must enter market");
    _;
  }

  modifier onlyMarket() {
    bool fromMarket = false;
    for (uint256 i = 0; i < markets.length; i++) {
      if (msg.sender == markets[i]) {
        fromMarket = true;
        break;
      }
    }
    require(fromMarket == true, "Core: caller should be market");
    _;
  }

  modifier onlyLeverager() {
    require(msg.sender == leverager, "Core: caller should be leverager");
    _;
  }

  /* ========== VIEWS ========== */

  function allMarkets() external view override returns (address[] memory) {
    return markets;
  }

  function marketInfoOf(address lToken) external view override returns (Constant.MarketInfo memory) {
    return marketInfos[lToken];
  }

  function marketListOf(address account) external view override returns (address[] memory) {
    return marketListOfUsers[account];
  }

  function checkMembership(address account, address lToken) external view override returns (bool) {
    return usersOfMarket[lToken][account];
  }

  function accountLiquidityOf(
    address account
  ) external view override returns (uint256 collateralInUSD, uint256 supplyInUSD, uint256 borrowInUSD) {
    return IValidator(validator).getAccountLiquidity(account);
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function enterMarkets(address[] memory lTokens) public override {
    for (uint256 i = 0; i < lTokens.length; i++) {
      _enterMarket(payable(lTokens[i]), msg.sender);
    }
  }

  function exitMarket(address lToken) external override onlyListedMarket(lToken) onlyMemberOfMarket(lToken) {
    Constant.AccountSnapshot memory snapshot = ILToken(lToken).accruedAccountSnapshot(msg.sender);
    require(snapshot.borrowBalance == 0, "Core: borrow balance must be zero");
    require(IValidator(validator).redeemAllowed(lToken, msg.sender, snapshot.lTokenBalance), "Core: cannot redeem");

    _removeUserMarket(lToken, msg.sender);
    emit MarketExited(lToken, msg.sender);
  }

  function supply(
    address lToken,
    uint256 uAmount
  ) external payable override onlyListedMarket(lToken) nonReentrant whenNotPaused returns (uint256) {
    uAmount = ILToken(lToken).underlying() == address(ETH) ? msg.value : uAmount;
    uint256 supplyCap = marketInfos[lToken].supplyCap;
    require(
      supplyCap == 0 ||
        ILToken(lToken).totalSupply().mul(ILToken(lToken).exchangeRate()).div(1e18).add(uAmount) <= supplyCap,
      "Core: supply cap reached"
    );

    uint256 lAmount = ILToken(lToken).supply{value: msg.value}(msg.sender, uAmount);
    labDistributor.notifySupplyUpdated(lToken, msg.sender);

    emit MarketSupply(msg.sender, lToken, uAmount);
    return lAmount;
  }

  function supplyBehalf(
    address supplier,
    address lToken,
    uint256 uAmount
  ) external payable override onlyListedMarket(lToken) nonReentrant whenNotPaused returns (uint256) {
    uAmount = ILToken(lToken).underlying() == address(ETH) ? msg.value : uAmount;
    uint256 supplyCap = marketInfos[lToken].supplyCap;
    require(
      supplyCap == 0 ||
        ILToken(lToken).totalSupply().mul(ILToken(lToken).exchangeRate()).div(1e18).add(uAmount) <= supplyCap,
      "Core: supply cap reached"
    );

    uint256 lAmount = ILToken(lToken).supplyBehalf{value: msg.value}(msg.sender, supplier, uAmount);
    labDistributor.notifySupplyUpdated(lToken, supplier);

    emit MarketSupply(supplier, lToken, uAmount);
    return lAmount;
  }

  function redeemToken(
    address lToken,
    uint256 lAmount
  ) external override onlyListedMarket(lToken) nonReentrant whenNotPaused returns (uint256) {
    uint256 uAmountRedeem = ILToken(lToken).redeemToken(msg.sender, lAmount);
    labDistributor.notifySupplyUpdated(lToken, msg.sender);

    emit MarketRedeem(msg.sender, lToken, uAmountRedeem);
    return uAmountRedeem;
  }

  function redeemUnderlying(
    address lToken,
    uint256 uAmount
  ) external override onlyListedMarket(lToken) nonReentrant whenNotPaused returns (uint256) {
    uint256 uAmountRedeem = ILToken(lToken).redeemUnderlying(msg.sender, uAmount);
    labDistributor.notifySupplyUpdated(lToken, msg.sender);

    emit MarketRedeem(msg.sender, lToken, uAmountRedeem);
    return uAmountRedeem;
  }

  function borrow(
    address lToken,
    uint256 amount
  ) external override onlyListedMarket(lToken) nonReentrant whenNotPaused {
    _enterMarket(lToken, msg.sender);
    require(IValidator(validator).borrowAllowed(lToken, msg.sender, amount), "Core: cannot borrow");

    ILToken(payable(lToken)).borrow(msg.sender, amount);
    labDistributor.notifyBorrowUpdated(lToken, msg.sender);
  }

  function borrowBehalf(
    address borrower,
    address lToken,
    uint256 amount
  ) external override onlyListedMarket(lToken) onlyLeverager nonReentrant whenNotPaused {
    _enterMarket(lToken, borrower);
    require(IValidator(validator).borrowAllowed(lToken, borrower, amount), "Core: cannot borrow");

    ILToken(payable(lToken)).borrowBehalf(msg.sender, borrower, amount);
    labDistributor.notifyBorrowUpdated(lToken, borrower);
  }

  function repayBorrow(
    address lToken,
    uint256 amount
  ) external payable override onlyListedMarket(lToken) nonReentrant whenNotPaused {
    ILToken(payable(lToken)).repayBorrow{value: msg.value}(msg.sender, amount);
    labDistributor.notifyBorrowUpdated(lToken, msg.sender);
  }

  function liquidateBorrow(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower,
    uint256 amount
  ) external payable override nonReentrant whenNotPaused {
    amount = ILToken(lTokenBorrowed).underlying() == address(ETH) ? msg.value : amount;
    require(marketInfos[lTokenBorrowed].isListed && marketInfos[lTokenCollateral].isListed, "Core: invalid market");
    require(usersOfMarket[lTokenCollateral][borrower], "Core: not a collateral");
    require(marketInfos[lTokenCollateral].collateralFactor > 0, "Core: not a collateral");
    require(
      IValidator(validator).liquidateAllowed(lTokenBorrowed, borrower, amount, closeFactor),
      "Core: cannot liquidate borrow"
    );

    (, uint256 rebateLAmount, uint256 liquidatorLAmount) = ILToken(lTokenBorrowed).liquidateBorrow{value: msg.value}(
      lTokenCollateral,
      msg.sender,
      borrower,
      amount
    );

    ILToken(lTokenCollateral).seize(msg.sender, borrower, liquidatorLAmount);
    labDistributor.notifyTransferred(lTokenCollateral, borrower, msg.sender);

    if (rebateLAmount > 0) {
      ILToken(lTokenCollateral).seize(rebateDistributor, borrower, rebateLAmount);
      labDistributor.notifyTransferred(lTokenCollateral, borrower, rebateDistributor);
    }

    labDistributor.notifyBorrowUpdated(lTokenBorrowed, borrower);
  }

  function claimLab() external override nonReentrant {
    labDistributor.claim(markets, msg.sender);
  }

  function claimLab(address market) external override nonReentrant {
    address[] memory _markets = new address[](1);
    _markets[0] = market;
    labDistributor.claim(_markets, msg.sender);
  }

  /// @notice 쌓인 보상을 Locker에 바로 deposit
  function compoundLab(uint256 lockDuration) external override nonReentrant {
    labDistributor.compound(markets, msg.sender, lockDuration);
  }

  function transferTokens(
    address spender,
    address src,
    address dst,
    uint256 amount
  ) external override nonReentrant onlyMarket {
    ILToken(msg.sender).transferTokensInternal(spender, src, dst, amount);
    labDistributor.notifyTransferred(msg.sender, src, dst);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _enterMarket(address lToken, address _account) internal onlyListedMarket(lToken) {
    if (!usersOfMarket[lToken][_account]) {
      usersOfMarket[lToken][_account] = true;
      marketListOfUsers[_account].push(lToken);
      emit MarketEntered(lToken, _account);
    }
  }

  function _removeUserMarket(address lTokenToExit, address _account) private {
    require(marketListOfUsers[_account].length > 0, "Core: cannot pop user market");
    delete usersOfMarket[lTokenToExit][_account];

    uint256 length = marketListOfUsers[_account].length;
    for (uint256 i = 0; i < length; i++) {
      if (marketListOfUsers[_account][i] == lTokenToExit) {
        marketListOfUsers[_account][i] = marketListOfUsers[_account][length - 1];
        marketListOfUsers[_account].pop();
        break;
      }
    }
  }
}

// SPDX-License-Identifier: UNLICENSE
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "./library/Constant.sol";

import "./interfaces/ICore.sol";
import "./interfaces/ILABDistributor.sol";
import "./interfaces/IPriceCalculator.sol";
import "./interfaces/ILToken.sol";
import "./interfaces/IRebateDistributor.sol";

abstract contract CoreAdmin is ICore, Ownable, ReentrancyGuard, Pausable {
  /* ========== STATE VARIABLES ========== */

  address public keeper;
  address public leverager;
  address public override validator;
  address public override rebateDistributor;
  ILABDistributor public labDistributor;
  IPriceCalculator public priceCalculator;

  address[] public markets; // lTokenAddress[]
  mapping(address => Constant.MarketInfo) public marketInfos; // (lTokenAddress => MarketInfo)

  uint256 public override closeFactor;
  uint256 public override liquidationIncentive;

  /* ========== MODIFIERS ========== */

  modifier onlyKeeper() {
    require(msg.sender == keeper || msg.sender == owner(), "Core: caller is not the owner or keeper");
    _;
  }

  modifier onlyListedMarket(address lToken) {
    require(marketInfos[lToken].isListed, "Core: invalid market");
    _;
  }

  /* ========== INITIALIZER ========== */

  function __Core_init() internal {
    closeFactor = 5e17; // 0.5
    liquidationIncentive = 115e16; // 1.15
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function setPriceCalculator(address _priceCalculator) external onlyKeeper {
    require(_priceCalculator != address(0), "Core: invalid calculator address");
    priceCalculator = IPriceCalculator(_priceCalculator);
  }

  /// @notice keeper address 변경
  /// @dev keeper address 에서만 요청 가능
  /// @param _keeper 새로운 keeper address
  function setKeeper(address _keeper) external onlyKeeper {
    require(_keeper != address(0), "Core: invalid keeper address");
    keeper = _keeper;
    emit KeeperUpdated(_keeper);
  }

  /// @notice validator 변경
  /// @dev keeper address 에서만 요청 가능
  /// @param _validator 새로운 validator address
  function setValidator(address _validator) external onlyKeeper {
    require(_validator != address(0), "Core: invalid validator address");
    validator = _validator;
    emit ValidatorUpdated(_validator);
  }

  /// @notice labDistributor 변경
  /// @dev keeper address 에서만 요청 가능
  /// @param _labDistributor 새로운 labDistributor address
  function setLABDistributor(address _labDistributor) external onlyKeeper {
    require(_labDistributor != address(0), "Core: invalid labDistributor address");
    labDistributor = ILABDistributor(_labDistributor);
    emit LABDistributorUpdated(_labDistributor);
  }

  function setRebateDistributor(address _rebateDistributor) external onlyKeeper {
    require(_rebateDistributor != address(0), "Core: invalid rebateDistributor address");
    rebateDistributor = _rebateDistributor;
    emit RebateDistributorUpdated(_rebateDistributor);
  }

  function setLeverager(address _leverager) external onlyKeeper {
    require(_leverager != address(0), "Core: invalid leverager address");
    leverager = _leverager;
    emit LeveragerUpdated(_leverager);
  }

  /// @notice close factor 변경
  /// @dev keeper address 에서만 요청 가능
  /// @param newCloseFactor 새로운 close factor 값 (TBD)
  function setCloseFactor(uint256 newCloseFactor) external onlyKeeper {
    require(
      newCloseFactor >= Constant.CLOSE_FACTOR_MIN && newCloseFactor <= Constant.CLOSE_FACTOR_MAX,
      "Core: invalid close factor"
    );
    closeFactor = newCloseFactor;
    emit CloseFactorUpdated(newCloseFactor);
  }

  function setCollateralFactor(
    address lToken,
    uint256 newCollateralFactor
  ) external onlyKeeper onlyListedMarket(lToken) {
    require(newCollateralFactor <= Constant.COLLATERAL_FACTOR_MAX, "Core: invalid collateral factor");
    if (newCollateralFactor != 0 && priceCalculator.getUnderlyingPrice(lToken) == 0) {
      revert("Core: invalid underlying price");
    }

    marketInfos[lToken].collateralFactor = newCollateralFactor;
    emit CollateralFactorUpdated(lToken, newCollateralFactor);
  }

  function setLiquidationIncentive(uint256 newLiquidationIncentive) external onlyKeeper {
    liquidationIncentive = newLiquidationIncentive;
    emit LiquidationIncentiveUpdated(newLiquidationIncentive);
  }

  function setMarketSupplyCaps(address[] calldata lTokens, uint256[] calldata newSupplyCaps) external onlyKeeper {
    require(lTokens.length != 0 && lTokens.length == newSupplyCaps.length, "Core: invalid data");

    for (uint256 i = 0; i < lTokens.length; i++) {
      marketInfos[lTokens[i]].supplyCap = newSupplyCaps[i];
      emit SupplyCapUpdated(lTokens[i], newSupplyCaps[i]);
    }
  }

  function setMarketBorrowCaps(address[] calldata lTokens, uint256[] calldata newBorrowCaps) external onlyKeeper {
    require(lTokens.length != 0 && lTokens.length == newBorrowCaps.length, "Core: invalid data");

    for (uint256 i = 0; i < lTokens.length; i++) {
      marketInfos[lTokens[i]].borrowCap = newBorrowCaps[i];
      emit BorrowCapUpdated(lTokens[i], newBorrowCaps[i]);
    }
  }

  function listMarket(
    address payable lToken,
    uint256 supplyCap,
    uint256 borrowCap,
    uint256 collateralFactor
  ) external onlyKeeper {
    require(!marketInfos[lToken].isListed, "Core: already listed market");
    for (uint256 i = 0; i < markets.length; i++) {
      require(markets[i] != lToken, "Core: already listed market");
    }

    marketInfos[lToken] = Constant.MarketInfo({
      isListed: true,
      supplyCap: supplyCap,
      borrowCap: borrowCap,
      collateralFactor: collateralFactor
    });
    markets.push(lToken);
    emit MarketListed(lToken);
  }

  function removeMarket(address payable lToken) external onlyKeeper {
    require(marketInfos[lToken].isListed, "Core: unlisted market");
    require(ILToken(lToken).totalSupply() == 0 && ILToken(lToken).totalBorrow() == 0, "Core: cannot remove market");

    uint256 length = markets.length;
    for (uint256 i = 0; i < length; i++) {
      if (markets[i] == lToken) {
        markets[i] = markets[length - 1];
        markets.pop();
        delete marketInfos[lToken];
        break;
      }
    }
  }

  function claimLabBehalf(address[] calldata accounts) external onlyKeeper nonReentrant {
    labDistributor.claimBehalf(markets, accounts);
  }

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

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

File 4 of 68 : ILToken.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../library/Constant.sol";

interface ILToken {
  function underlying() external view returns (address);

  function totalSupply() external view returns (uint256);

  function accountSnapshot(address account) external view returns (Constant.AccountSnapshot memory);

  function underlyingBalanceOf(address account) external view returns (uint256);

  function borrowBalanceOf(address account) external view returns (uint256);

  function totalBorrow() external view returns (uint256);

  function _totalBorrow() external view returns (uint256);

  function totalReserve() external view returns (uint256);

  function reserveFactor() external view returns (uint256);

  function lastAccruedTime() external view returns (uint256);

  function accInterestIndex() external view returns (uint256);

  function exchangeRate() external view returns (uint256);

  function getCash() external view returns (uint256);

  function getRateModel() external view returns (address);

  function getAccInterestIndex() external view returns (uint256);

  function accruedAccountSnapshot(address account) external returns (Constant.AccountSnapshot memory);

  function accruedBorrowBalanceOf(address account) external returns (uint256);

  function accruedTotalBorrow() external returns (uint256);

  function accruedExchangeRate() external returns (uint256);

  function approve(address spender, uint256 amount) external returns (bool);

  function allowance(address owner, address spender) external view returns (uint256);

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

  function transfer(address dst, uint256 amount) external returns (bool);

  function transferFrom(address src, address dst, uint256 amount) external returns (bool);

  function supply(address account, uint256 underlyingAmount) external payable returns (uint256);

  function supplyBehalf(address account, address supplier, uint256 underlyingAmount) external payable returns (uint256);

  function redeemToken(address account, uint256 lTokenAmount) external returns (uint256);

  function redeemUnderlying(address account, uint256 underlyingAmount) external returns (uint256);

  function borrow(address account, uint256 amount) external returns (uint256);

  function borrowBehalf(address account, address borrower, uint256 amount) external returns (uint256);

  function repayBorrow(address account, uint256 amount) external payable returns (uint256);

  function liquidateBorrow(
    address lTokenCollateral,
    address liquidator,
    address borrower,
    uint256 amount
  ) external payable returns (uint256 seizeLAmount, uint256 rebateLAmount, uint256 liquidatorLAmount);

  function seize(address liquidator, address borrower, uint256 lTokenAmount) external;

  function withdrawReserves() external;

  function transferTokensInternal(address spender, address src, address dst, uint256 amount) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IValidator {
  function redeemAllowed(address lToken, address redeemer, uint256 redeemAmount) external returns (bool);

  function borrowAllowed(address lToken, address borrower, uint256 borrowAmount) external returns (bool);

  function liquidateAllowed(
    address lTokenBorrowed,
    address borrower,
    uint256 repayAmount,
    uint256 closeFactor
  ) external returns (bool);

  function lTokenAmountToSeize(
    address lTokenBorrowed,
    address lTokenCollateral,
    uint256 actualRepayAmount
  ) external returns (uint256 seizeLAmount, uint256 rebateLAmount, uint256 liquidatorLAmount);

  function getAccountLiquidity(
    address account
  ) external view returns (uint256 collateralInUSD, uint256 supplyInUSD, uint256 borrowInUSD);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

interface IPriceCalculator {
  struct ReferenceData {
    uint256 lastData;
    uint256 lastUpdated;
  }

  function priceOf(address asset) external view returns (uint256);

  function pricesOf(address[] memory assets) external view returns (uint256[] memory);

  function priceOfETH() external view returns (uint256);

  function getUnderlyingPrice(address lToken) external view returns (uint256);

  function getUnderlyingPrices(address[] memory lTokens) external view returns (uint256[] memory);
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
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) {
        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) {
        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) {
        // 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) {
        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) {
        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) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    /**
     * @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) {
        require(b <= a, "SafeMath: subtraction overflow");
        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) {
        if (a == 0) return 0;
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    /**
     * @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. 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) internal pure returns (uint256) {
        require(b > 0, "SafeMath: division by zero");
        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) {
        require(b > 0, "SafeMath: modulo by zero");
        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) {
        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.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryDiv}.
     *
     * 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) {
        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) {
        require(b > 0, errorMessage);
        return a % b;
    }
}

File 8 of 68 : Constant.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

library Constant {
  uint256 public constant CLOSE_FACTOR_MIN = 5e16;
  uint256 public constant CLOSE_FACTOR_MAX = 9e17;
  uint256 public constant COLLATERAL_FACTOR_MAX = 9e17;
  uint256 public constant LIQUIDATION_THRESHOLD_MAX = 9e17;
  uint256 public constant LIQUIDATION_BONUS_MAX = 5e17;

  enum EcoScorePreviewOption {
    LOCK,
    CLAIM,
    EXTEND,
    LOCK_MORE
  }

  enum LoanState {
    None,
    Active,
    Auction,
    Repaid,
    Defaulted
  }

  struct MarketInfo {
    bool isListed;
    uint256 supplyCap;
    uint256 borrowCap;
    uint256 collateralFactor;
  }

  struct BorrowInfo {
    uint256 borrow;
    uint256 interestIndex;
  }

  struct AccountSnapshot {
    uint256 lTokenBalance;
    uint256 borrowBalance;
    uint256 exchangeRate;
  }

  struct AccrueSnapshot {
    uint256 totalBorrow;
    uint256 totalReserve;
    uint256 accInterestIndex;
  }

  struct AccrueLoanSnapshot {
    uint256 totalBorrow;
    uint256 accInterestIndex;
  }

  struct DistributionInfo {
    uint256 supplySpeed;
    uint256 borrowSpeed;
    uint256 totalBoostedSupply;
    uint256 totalBoostedBorrow;
    uint256 accPerShareSupply;
    uint256 accPerShareBorrow;
    uint256 accruedAt;
  }

  struct DistributionAccountInfo {
    uint256 accuredLAB; // Unclaimed LAB rewards amount
    uint256 boostedSupply; // effective(boosted) supply balance of user  (since last_action)
    uint256 boostedBorrow; // effective(boosted) borrow balance of user  (since last_action)
    uint256 accPerShareSupply; // Last integral value of LAB rewards per share. ∫(LABRate(t) / totalShare(t) dt) from 0 till (last_action)
    uint256 accPerShareBorrow; // Last integral value of LAB rewards per share. ∫(LABRate(t) / totalShare(t) dt) from 0 till (last_action)
  }

  struct DistributionAPY {
    uint256 apySupplyLab;
    uint256 apyBorrowLab;
    uint256 apyAccountSupplyLab;
    uint256 apyAccountBorrowLab;
  }

  struct RebateCheckpoint {
    uint256 timestamp;
    uint256 totalScore;
    uint256 adminFeeRate;
    uint256 weeklyLabSpeed;
    uint256 additionalLabAmount;
    mapping(address => uint256) marketFees;
  }

  struct LockInfo {
    uint256 timestamp;
    uint256 amount;
    uint256 expiry;
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../library/Constant.sol";

interface ICore {
  /* ========== Event ========== */
  event MarketSupply(address user, address lToken, uint256 uAmount);
  event MarketRedeem(address user, address lToken, uint256 uAmount);

  event MarketListed(address lToken);
  event MarketEntered(address lToken, address account);
  event MarketExited(address lToken, address account);

  event CloseFactorUpdated(uint256 newCloseFactor);
  event CollateralFactorUpdated(address lToken, uint256 newCollateralFactor);
  event LiquidationIncentiveUpdated(uint256 newLiquidationIncentive);
  event SupplyCapUpdated(address indexed lToken, uint256 newSupplyCap);
  event BorrowCapUpdated(address indexed lToken, uint256 newBorrowCap);
  event KeeperUpdated(address newKeeper);
  event ValidatorUpdated(address newValidator);
  event LABDistributorUpdated(address newLABDistributor);
  event RebateDistributorUpdated(address newRebateDistributor);
  event LeveragerUpdated(address newLeverager);
  event FlashLoan(
    address indexed target,
    address indexed initiator,
    address indexed asset,
    uint256 amount,
    uint256 premium
  );

  function validator() external view returns (address);

  function rebateDistributor() external view returns (address);

  function allMarkets() external view returns (address[] memory);

  function marketListOf(address account) external view returns (address[] memory);

  function marketInfoOf(address lToken) external view returns (Constant.MarketInfo memory);

  function checkMembership(address account, address lToken) external view returns (bool);

  function accountLiquidityOf(
    address account
  ) external view returns (uint256 collateralInUSD, uint256 supplyInUSD, uint256 borrowInUSD);

  function closeFactor() external view returns (uint256);

  function liquidationIncentive() external view returns (uint256);

  function enterMarkets(address[] memory lTokens) external;

  function exitMarket(address lToken) external;

  function supply(address lToken, uint256 underlyingAmount) external payable returns (uint256);

  function supplyBehalf(address account, address lToken, uint256 underlyingAmount) external payable returns (uint256);

  function redeemToken(address lToken, uint256 lTokenAmount) external returns (uint256 redeemed);

  function redeemUnderlying(address lToken, uint256 underlyingAmount) external returns (uint256 redeemed);

  function borrow(address lToken, uint256 amount) external;

  function borrowBehalf(address borrower, address lToken, uint256 amount) external;

  function repayBorrow(address lToken, uint256 amount) external payable;

  function liquidateBorrow(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower,
    uint256 amount
  ) external payable;

  function claimLab() external;

  function claimLab(address market) external;

  function transferTokens(address spender, address src, address dst, uint256 amount) external;

  function compoundLab(uint256 lockDuration) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../library/Constant.sol";

interface ILABDistributor {
  /* ========== EVENTS ========== */
  event DistributionSpeedUpdated(address indexed lToken, uint256 supplySpeed, uint256 borrowSpeed);
  event Claimed(address indexed user, uint256 amount);
  event Compound(address indexed user, uint256 amount);

  function accuredLAB(address[] calldata markets, address account) external view returns (uint);

  function distributionInfoOf(address market) external view returns (Constant.DistributionInfo memory);

  function accountDistributionInfoOf(
    address market,
    address account
  ) external view returns (Constant.DistributionAccountInfo memory);

  function apyDistributionOf(address market, address account) external view returns (Constant.DistributionAPY memory);

  function boostedRatioOf(
    address market,
    address account
  ) external view returns (uint boostedSupplyRatio, uint boostedBorrowRatio);

  function notifySupplyUpdated(address market, address user) external;

  function notifyBorrowUpdated(address market, address user) external;

  function notifyTransferred(address qToken, address sender, address receiver) external;

  function claim(address[] calldata markets, address account) external;

  function claimBehalf(address[] calldata markets, address[] calldata accounts) external;

  function updateAccountBoostedInfo(address user) external;

  function compound(address[] calldata markets, address account, uint256 lockDuration) external;

  function pause() external;

  function unpause() external;

  function approve(address _spender, uint256 amount) external returns (bool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

interface IRebateDistributor {
  function setKeeper(address _keeper) external;

  function pause() external;

  function unpause() external;

  function updateAdminFeeRate(uint256 newAdminFeeRate) external;

  function checkpoint() external;

  function weeklyRebatePool() external view returns (uint256);

  function weeklyProfitOfVP(uint256 vp) external view returns (uint256);

  function weeklyProfitOf(address account) external view returns (uint256);

  function indicativeAPR() external view returns (uint256);

  function indicativeAPROf(uint256 amount, uint256 lockDuration) external view returns (uint256);

  function indicativeAPROfUser(address account) external view returns (uint256);

  function accruedRebates(address account) external view returns (uint256, uint256, uint256[] memory);

  function claimRebates() external returns (uint256, uint256, uint256[] memory);

  function claimAdminRebates() external returns (uint256, uint256[] memory);

  function addLABToRebatePool(uint256 amount) external;

  function addMarketUTokenToRebatePool(address lToken, uint256 uAmount) external payable;
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

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

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

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

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

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

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

File 13 of 68 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

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

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

    uint256 private _status;

    constructor () internal {
        _status = _NOT_ENTERED;
    }

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

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

        _;

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

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "./Context.sol";

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

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

    bool private _paused;

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

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

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

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

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

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

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../../utils/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

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

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
     * a default value of 18.
     *
     * To select a different value for {decimals}, use {_setupDecimals}.
     *
     * All three of these values are immutable: they can only be set once during
     * construction.
     */
    constructor (string memory name_, string memory symbol_) public {
        _name = name_;
        _symbol = symbol_;
        _decimals = 18;
    }

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

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

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

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

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

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

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

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

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

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

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

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

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

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

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

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

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

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

    /**
     * @dev Sets {decimals} to a value other than the default one of 18.
     *
     * WARNING: This function should only be called from the constructor. Most
     * applications that interact with token contracts will not expect
     * {decimals} to ever change, and may work incorrectly if it does.
     */
    function _setupDecimals(uint8 decimals_) internal virtual {
        _decimals = decimals_;
    }

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

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <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);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

import "../interfaces/IxLABMigration.sol";
import "../interfaces/IxLAB.sol";
import "../interfaces/IPausableXLAB.sol";

import "../library/SafeToken.sol";

contract xLABMigration is IxLABMigration, Ownable, ReentrancyGuard, Pausable {
    using SafeMath for uint256;
    using SafeToken for address;

    /* ========== STATE VARIABLES ========== */

    bool public initialized;
    address public ulab;
    address public xLAB;
    address public xULAB;

    // Migration start time
    uint256 public startTime;
    // Migration end time
    uint256 public endTime;

    uint256 public exchangeRate;

    mapping(address => bool) public isClaimed;

    /* ========== INITIALIZER ========== */

    constructor() public {}

    /* ========== RESTRICTED FUNCTIONS ========== */

    function initialize(address _ulab, address _xLAB, address _xULAB, uint256 _startTime, uint256 _endTime, uint256 _exchangeRate) external onlyOwner {
        require(initialized == false, "already initialized");
        require(_ulab != address(0), "invalid ulab address");
        require(_xLAB != address(0), "invalid xLAB address");
        require(_xULAB != address(0), "invalid xULAB address");
        require(_startTime > 0, "invalid start time");
        require(_endTime > _startTime, "invalid end time");
        require(_exchangeRate > 0, "invalid exchangeRate");

        ulab = _ulab;
        xLAB = _xLAB;
        xULAB = _xULAB;
        startTime = _startTime;
        endTime = _endTime;
        exchangeRate = _exchangeRate;

        _approveToken();

        initialized = true;
    }

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

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

    /* ========== MUTATIVE FUNCTIONS ========== */

    function migrate() external override nonReentrant whenNotPaused {
        require(IPausableXLAB(xLAB).paused(), "The xLAB must be in the pause state");
        uint256 _currentTimestamp = block.timestamp;
        require(_currentTimestamp >= startTime && _currentTimestamp <= endTime, "not migration time");
        require(isClaimed[msg.sender] == false, "already claimed");

        uint256 _lockedBalance = IxLAB(xLAB).lockedBalanceOf(msg.sender);
        require(_lockedBalance > 0, "invalid locked amount");

        IxLAB.LockInfo[] memory locks = IxLAB(xLAB).locksOf(msg.sender);

        for (uint256 i = 0; i < locks.length; i++) {
            uint256 _lockedAmount = locks[i].lockedAmount;
            uint256 _unlockTime = locks[i].unlockTime;

            uint256 _migrationAmount = _lockedAmount.mul(exchangeRate).div(1e18);

            if (_unlockTime <= _currentTimestamp) { // already unlocked, Set the lock up period to 7 days for migration
                IxLAB(xULAB).lockBehalf(msg.sender, _migrationAmount, 7 days);
            } else {
                uint256 _timeDiff = _unlockTime.sub(_currentTimestamp);
                if (_timeDiff > 7 days) {
                    IxLAB(xULAB).lockBehalf(msg.sender, _migrationAmount, _timeDiff); // lockup set for more than 7 days
                } else {
                    IxLAB(xULAB).lockBehalf(msg.sender, _migrationAmount, 7 days); //  lockup set for less than 7 days
                }
            }
            emit Migrate(msg.sender, _lockedAmount, _migrationAmount);
        }
        isClaimed[msg.sender] = true;
    }

    /* ========== PRIVATE FUNCTIONS ========== */

    function _approveToken() private {
        ulab.safeApprove(xULAB, uint256(-1));
    }

    /* ========== VIEWS ========== */

    function eligible(address account) external view override returns (bool) {
        bool _validTime = false;
        bool _isPause = IPausableXLAB(xLAB).paused();
        uint256 _lockedBalance = IxLAB(xLAB).lockedBalanceOf(account);

        uint256 _currentTimestamp = block.timestamp;
        if (_currentTimestamp >= startTime && _currentTimestamp <= endTime) {
            _validTime = true;
        }
        if (isClaimed[account] == false && _lockedBalance > 0 && _validTime && _isPause) {
            return true;
        } else {
            return false;
        }
    }

    function migrationAmount(address account) external override view returns (uint256, uint256) {
        uint256 _lockedBalance = IxLAB(xLAB).lockedBalanceOf(account);
        uint256 _currentTimestamp = block.timestamp;
        if (_lockedBalance > 0) {
            uint256 _totalUlabAmount = 0;
            uint256 _totalXUlabAmount = 0;
            IxLAB.LockInfo[] memory locks = IxLAB(xLAB).locksOf(account);
            for (uint256 i = 0; i < locks.length; i++) {
                uint256 _lockedAmount = locks[i].lockedAmount;
                uint256 _unlockTime = locks[i].unlockTime;

                uint256 _migrationAmount = _lockedAmount.mul(exchangeRate).div(1e18);
                _totalUlabAmount = _totalUlabAmount + _migrationAmount;

                if (_unlockTime <= _currentTimestamp) { // already unlocked, Set the lock up period to 7 days for migration
                    _totalXUlabAmount = _totalXUlabAmount + IxLAB(xULAB).calcVeAmount(_migrationAmount, 7 days);
                } else {
                    uint256 _timeDiff = _unlockTime.sub(_currentTimestamp);
                    if (_timeDiff > 7 days) {
                        _totalXUlabAmount = _totalXUlabAmount + IxLAB(xULAB).calcVeAmount(_migrationAmount, _timeDiff);
                    } else {
                        _totalXUlabAmount = _totalXUlabAmount + IxLAB(xULAB).calcVeAmount(_migrationAmount, 7 days);
                    }
                }
            }
            return (_totalUlabAmount, _totalXUlabAmount);
        } else {
            return (0, 0);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IxLABMigration {
    event Migrate(address indexed user, uint256 xlabAmount, uint256 xulabAmount);

    function migrate() external;
    function eligible(address account) external view returns (bool);
    function migrationAmount(address account) external view returns (uint256, uint256);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IxLAB {
  struct LockInfo {
    uint48 unlockTime;
    uint256 lockedAmount;
    uint256 veAmount;
  }

  struct BalanceInfo {
    uint256 balance;
    uint256 timestamp;
  }

  struct User {
    LockInfo[] locks;
    BalanceInfo[] balanceHistory;
  }

  function locksOf(address account) external view returns (LockInfo[] memory);

  function balanceHistoryOf(address account) external view returns (BalanceInfo[] memory);

  function calcVeAmount(uint256 amount, uint256 lockDuration) external pure returns (uint256);

  function shareOf(address account) external view returns (uint256);

  function balanceOfAt(address account, uint256 timestamp) external view returns (uint256);

  function lockedBalanceOf(address account) external view returns (uint256);

  function lock(uint256 amount, uint256 lockDuration) external;

  function lockBehalf(address account, uint256 amount, uint256 lockDuration) external;

  function unlock(uint256 slot) external;

  function unlockBehalf(address account, uint256 slot) external;

  function extendLock(uint256 slot, uint256 lockDuration) external;
}

File 21 of 68 : IPausableXLAB.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

interface IPausableXLAB {
    function paused() external view returns (bool);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.12;

interface ERC20Interface {
  function balanceOf(address user) external view returns (uint256);
}

library SafeToken {
  function myBalance(address token) internal view returns (uint256) {
    return ERC20Interface(token).balanceOf(address(this));
  }

  function balanceOf(address token, address user) internal view returns (uint256) {
    return ERC20Interface(token).balanceOf(user);
  }

  function safeApprove(address token, address to, uint256 value) internal {
    // bytes4(keccak256(bytes('approve(address,uint256)')));
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeApprove");
  }

  function safeTransfer(address token, address to, uint256 value) internal {
    // bytes4(keccak256(bytes('transfer(address,uint256)')));
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeTransfer");
  }

  function safeTransferFrom(address token, address from, address to, uint256 value) internal {
    // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), "!safeTransferFrom");
  }

  function safeTransferETH(address to, uint256 value) internal {
    (bool success, ) = to.call{value: value}(new bytes(0));
    require(success, "!safeTransferETH");
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "./UntransferableERC20.sol";
import "../interfaces/IxLAB.sol";
import "../interfaces/ILABDistributor.sol";
import "../library/SafeToken.sol";

contract xLAB is IxLAB, Ownable, ReentrancyGuard, Pausable, UntransferableERC20 {
  using SafeMath for uint256;
  using SafeToken for address;

  /* ========== CONSTANTS ============= */

  uint256 public constant MAX_LOCK_DURATION = 2 * 365 days;
  uint256 public constant MIN_LOCK_DURATION = 7 days;
  uint256 public constant MAX_LOCK_COUNT = 100000;

  /* ========== STATE VARIABLES ========== */

  bool public initialized;
  address public LAB;
  ILABDistributor public labDistributor;
  mapping(address => User) private users;

  /* ========== EVENTS ========== */

  event Lock(address account, uint256 unlockTime, uint256 lockAmount, uint256 veAmount);
  event Unlock(address account, uint256 unlockTime, uint256 lockAmount, uint256 veAmount);
  event ExtendLock(
    address account,
    uint256 slot,
    uint256 unlockTime,
    uint256 lockedAmount,
    uint256 originalVeAmount,
    uint256 newVeAmount
  );

  /* ========== INITIALIZER ========== */

  constructor() public {
    __UntransferableERC20_init("xLAB.s", "xLAB.s");
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function initialize(address _lab, address _labDistributor) external onlyOwner {
    require(initialized == false, "xLAB: already initialized");
    require(_lab != address(0), "xLAB: invalid lab address");
    require(_labDistributor != address(0), "xLAB: invalid labDistributor address");

    LAB = _lab;
    labDistributor = ILABDistributor(_labDistributor);

    initialized = true;
  }

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

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

  /* ========== VIEWS ========== */

  function locksOf(address account) external view override returns (LockInfo[] memory) {
    return users[account].locks;
  }

  function balanceHistoryOf(address account) external view override returns (BalanceInfo[] memory) {
    return users[account].balanceHistory;
  }

  function calcVeAmount(uint256 amount, uint256 lockDuration) public pure override returns (uint256) {
    if (amount == 0 || lockDuration == 0) return uint256(0);
    return amount.mul(lockDuration).div(365 days);
  }

  function shareOf(address account) public view override returns (uint256) {
    uint256 total = totalSupply();
    uint256 balance = balanceOf(account);

    if (total == 0 || balance == 0) return uint256(0);
    return balance.mul(1e18).div(total);
  }

  function balanceOfAt(address account, uint256 timestamp) external view override returns (uint256) {
    uint256 count = users[account].balanceHistory.length;
    if (count == 0) return uint256(0);

    for (uint256 i = count - 1; i < uint256(-1); i--) {
      BalanceInfo memory history = users[account].balanceHistory[i];

      if (history.timestamp <= timestamp) {
        return history.balance;
      }
    }

    return uint256(0);
  }

  function lockedBalanceOf(address account) external view override returns (uint256 total) {
    LockInfo[] memory locks = users[account].locks;

    for (uint256 i = 0; i < locks.length; i++) {
      total = total.add(locks[i].lockedAmount);
    }
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function lock(uint256 amount, uint256 lockDuration) external override {
    _lock(msg.sender, amount, lockDuration);
  }

  function lockBehalf(address account, uint256 amount, uint256 lockDuration) external override {
    _lock(account, amount, lockDuration);
  }

  function unlock(uint256 slot) external override {
    _unlock(msg.sender, slot);
  }

  function unlockBehalf(address account, uint256 slot) external override {
    _unlock(account, slot);
  }

  function extendLock(uint256 slot, uint256 lockDuration) external override {
    _extendLock(msg.sender, slot, lockDuration);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _lock(address account, uint256 amount, uint256 lockDuration) private nonReentrant whenNotPaused {
    require(amount > 0, "amount should greater than zero");
    require(lockDuration >= MIN_LOCK_DURATION && lockDuration <= MAX_LOCK_DURATION, "lockDuration is out of range");
    require(users[account].locks.length < MAX_LOCK_COUNT, "user lock count has reached full");

    uint256 unlockTime = block.timestamp + lockDuration;
    uint256 veAmount = calcVeAmount(amount, lockDuration);

    users[account].locks.push(LockInfo(uint48(unlockTime), amount, veAmount));

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

    _mint(account, veAmount);
    _updateUserBalanceHistory(account);
    _updateLABDistributorBoostedInfo(account);

    emit Lock(account, unlockTime, amount, veAmount);
  }

  function _unlock(address account, uint256 slot) private nonReentrant whenNotPaused {
    uint256 lockCount = users[account].locks.length;
    require(lockCount > 0, "no locks to unlock");
    require(slot < lockCount, "invalid slot");

    LockInfo memory lockInfo = users[account].locks[slot];
    require(uint256(lockInfo.unlockTime) <= block.timestamp, "unlock time is not over");

    if (slot != lockCount - 1) {
      users[account].locks[slot] = users[account].locks[lockCount - 1];
    }
    users[account].locks.pop();

    LAB.safeTransfer(account, lockInfo.lockedAmount);

    _burn(account, lockInfo.veAmount);
    _updateUserBalanceHistory(account);
    _updateLABDistributorBoostedInfo(account);

    emit Unlock(account, lockInfo.unlockTime, lockInfo.lockedAmount, lockInfo.veAmount);
  }

  function _extendLock(address account, uint256 slot, uint256 lockDuration) private nonReentrant whenNotPaused {
    require(lockDuration >= MIN_LOCK_DURATION && lockDuration <= MAX_LOCK_DURATION, "lockDuration is out of range");

    uint256 lockCount = users[account].locks.length;
    require(slot < lockCount, "invalid slot");

    uint256 originalUnlockTime = uint256(users[account].locks[slot].unlockTime);
    uint256 lockedAmount = uint256(users[account].locks[slot].lockedAmount);
    uint256 originalVeAmount = uint256(users[account].locks[slot].veAmount);
    uint256 newUnlockTime = block.timestamp + lockDuration;
    uint256 newVeAmount = calcVeAmount(lockedAmount, lockDuration);

    require(originalUnlockTime < newUnlockTime && originalVeAmount < newVeAmount, "invalid lockDuration");

    users[account].locks[slot].unlockTime = uint48(newUnlockTime);
    users[account].locks[slot].veAmount = newVeAmount;

    _mint(account, newVeAmount.sub(originalVeAmount));
    _updateUserBalanceHistory(account);
    _updateLABDistributorBoostedInfo(account);

    emit ExtendLock(account, slot, newUnlockTime, lockedAmount, originalVeAmount, newVeAmount);
  }

  function _updateUserBalanceHistory(address account) private {
    uint256 balance = balanceOf(account);
    users[account].balanceHistory.push(BalanceInfo(balance, block.timestamp));
  }

  function _updateLABDistributorBoostedInfo(address account) private {
    labDistributor.updateAccountBoostedInfo(account);
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";

abstract contract UntransferableERC20 {
  using SafeMath for uint256;

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

  string private _name;
  string private _symbol;
  uint256 private _totalSupply;

  event Burn(address indexed account, uint256 value);
  event Mint(address indexed beneficiary, uint256 value);

  function __UntransferableERC20_init(string memory name_, string memory symbol_) internal {
    _name = name_;
    _symbol = symbol_;
  }

  function name() external view virtual returns (string memory) {
    return _name;
  }

  function symbol() external view virtual returns (string memory) {
    return _symbol;
  }

  function decimals() external pure virtual returns (uint8) {
    return 18;
  }

  function totalSupply() public view virtual returns (uint256) {
    return _totalSupply;
  }

  function balanceOf(address account) public view virtual returns (uint256) {
    return _balances[account];
  }

  function _mint(address account, uint256 amount) internal virtual {
    require(account != address(0), "UntransferableERC20: mint to the zero address");

    _totalSupply += amount;
    _balances[account] += amount;

    emit Mint(account, amount);
  }

  function _burn(address account, uint256 amount) internal virtual {
    require(account != address(0), "UntransferableERC20: burn from the zero address");

    uint256 accountBalance = _balances[account];
    require(accountBalance >= amount, "UntransferableERC20: burn amount exceeds balance");
    _balances[account] = accountBalance.sub(amount);
    _totalSupply = _totalSupply.sub(amount);

    emit Burn(account, amount);
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "../library/SafeToken.sol";

import "../interfaces/IRewardController.sol";
import "../interfaces/ILABDistributor.sol";
import "../interfaces/IxLAB.sol";

contract RewardController is IRewardController, Ownable, ReentrancyGuard, Pausable {
  using SafeMath for uint256;
  using SafeToken for address;

  uint256 public constant QUART = 25000; //  25%
  uint256 public constant HALF = 65000; //  65%
  uint256 public constant WHOLE = 100000; // 100%

  address public constant DEAD = 0x000000000000000000000000000000000000dEaD;

  /// @notice Proportion of burn amount
  uint256 public burn;

  /// @notice Duration of vesting LAB
  uint256 public vestDuration;

  address public LAB;
  address public treasury;

  IxLAB public xLAB;
  ILABDistributor public labDistributor;

  /********************** Lock & Earn Info ***********************/

  mapping(address => Balances) private balances;
  mapping(address => LockedBalance[]) private userEarnings;
  mapping(address => bool) public minters;

  bool public mintersAreSet;

  bool public initialized;

  constructor() public {}

  function initialize(
    address _lab,
    address _xlab,
    address _labDistributor,
    address _treasury,
    uint256 _vestDuration,
    uint256 _burnRatio
  ) external onlyOwner {
    require(initialized == false, "already initialized");
    require(_lab != address(0), "RewardController: lab address can't be zero");
    require(_xlab != address(0), "RewardController: xlab address can't be zero");
    require(_labDistributor != address(0), "RewardController: labDistributor can't be zero");
    require(_treasury != address(0), "RewardController: treasury address can't be zero");
    require(_vestDuration != uint256(0), "RewardController: vestDuration can't be zero");
    require(_burnRatio <= WHOLE, "RewardController: invalid burn");

    LAB = _lab;
    xLAB = IxLAB(_xlab);
    labDistributor = ILABDistributor(_labDistributor);
    treasury = _treasury;
    burn = _burnRatio;
    vestDuration = _vestDuration;

    _approveLAB(_xlab);

    initialized = true;
  }

  /* ========== RESTRICTED FUNCTIONS ========== */
  function setMinters(address[] memory _minters) external onlyOwner {
    require(!mintersAreSet, "minters set");
    for (uint256 i = 0; i < _minters.length; i++) {
      require(_minters[i] != address(0), "minter is 0 address");
      minters[_minters[i]] = true;
    }
    mintersAreSet = true;
  }

  function setVestDuration(uint256 _vestDuration) external onlyOwner {
    require(_vestDuration > 0, "RewardController: invalid vest duration");
    vestDuration = _vestDuration;
  }

  function setXLAB(address _xlab) external onlyOwner {
    if (address(xLAB) != address(0)) {
      _disapproveLAB(address(xLAB));
    }
    xLAB = IxLAB(_xlab);
    _approveLAB(_xlab);
  }

  function pause() public onlyOwner {
    _pause();
  }

  function unpause() public onlyOwner {
    _unpause();
  }

  /* ========== VIEWS ========== */

  function earnedBalances(
    address user
  ) public view override returns (uint256 total, uint256 unlocked, EarnedBalance[] memory earningsData) {
    unlocked = balances[user].unlocked;
    LockedBalance[] storage earnings = userEarnings[user];
    uint256 idx;
    for (uint256 i = 0; i < earnings.length; i++) {
      if (earnings[i].unlockTime > block.timestamp) {
        if (idx == 0) {
          earningsData = new EarnedBalance[](earnings.length - i);
        }
        (, , uint256 penaltyAmount, ) = _penaltyInfo(userEarnings[user][i]);
        earningsData[idx].amount = earnings[i].amount;
        earningsData[idx].unlockTime = earnings[i].unlockTime;
        earningsData[idx].penalty = penaltyAmount;
        idx++;
        total = total.add(earnings[i].amount);
      } else {
        unlocked = unlocked.add(earnings[i].amount);
      }
    }
    return (total, unlocked, earningsData);
  }

  function withdrawableBalance(
    address user
  ) public view override returns (uint256 amount, uint256 penaltyAmount, uint256 burnAmount) {
    uint256 earned = balances[user].earned;
    if (earned > 0) {
      uint256 length = userEarnings[user].length;
      for (uint256 i = 0; i < length; i++) {
        uint256 earnedAmount = userEarnings[user][i].amount;
        if (earnedAmount == 0) continue;
        (, , uint256 newPenaltyAmount, uint256 newBurnAmount) = _penaltyInfo(userEarnings[user][i]);
        penaltyAmount = penaltyAmount.add(newPenaltyAmount);
        burnAmount = burnAmount.add(newBurnAmount);
      }
    }
    amount = balances[user].unlocked.add(earned).sub(penaltyAmount);
    return (amount, penaltyAmount, burnAmount);
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function mint(address user, uint256 amount, bool withPenalty) external override whenNotPaused nonReentrant {
    require(minters[msg.sender], "!minter");
    if (amount == 0) return;

    Balances storage bal = balances[user];
    bal.total = bal.total.add(amount);

    if (withPenalty) {
      bal.earned = bal.earned.add(amount);
      LockedBalance[] storage earnings = userEarnings[user];
      uint256 unlockTime = block.timestamp.add(vestDuration);
      earnings.push(LockedBalance({amount: amount, unlockTime: unlockTime, duration: vestDuration}));
    } else {
      bal.unlocked = bal.unlocked.add(amount);
    }
  }

  function withdraw(uint256 amount) external override nonReentrant {
    address _address = msg.sender;
    require(amount != 0, "amount cannot be 0");

    uint256 penaltyAmount;
    uint256 burnAmount;
    Balances storage bal = balances[_address];

    if (amount <= bal.unlocked) {
      bal.unlocked = bal.unlocked.sub(amount);
    } else {
      uint256 remaining = amount.sub(bal.unlocked);
      require(bal.earned >= remaining, "invalid earned");
      bal.unlocked = 0;
      uint256 sumEarned = bal.earned;
      uint256 i;
      for (i = 0; ; i++) {
        uint256 earnedAmount = userEarnings[_address][i].amount;
        if (earnedAmount == 0) continue;
        (, uint256 penaltyFactor, , ) = _penaltyInfo(userEarnings[_address][i]);

        // Amount required from this lock, taking into account the penalty
        uint256 requiredAmount = remaining.mul(WHOLE).div(WHOLE.sub(penaltyFactor));
        if (requiredAmount >= earnedAmount) {
          requiredAmount = earnedAmount;
          remaining = remaining.sub(earnedAmount.mul(WHOLE.sub(penaltyFactor)).div(WHOLE));
          if (remaining == 0) i++;
        } else {
          userEarnings[_address][i].amount = earnedAmount.sub(requiredAmount);
          remaining = 0;
        }
        sumEarned = sumEarned.sub(requiredAmount);
        penaltyAmount = penaltyAmount.add(requiredAmount.mul(penaltyFactor).div(WHOLE));

        if (remaining == 0) {
          break;
        } else {
          require(sumEarned != 0, "0 earned");
        }
      }
      if (i > 0) {
        for (uint256 j = i; j < userEarnings[_address].length; j++) {
          userEarnings[_address][j - i] = userEarnings[_address][j];
        }
        for (uint256 j = 0; j < i; j++) {
          userEarnings[_address].pop();
        }
      }
      bal.earned = sumEarned;
    }

    bal.total = bal.total.sub(amount).sub(penaltyAmount);
    burnAmount = penaltyAmount.mul(burn).div(WHOLE);
    _withdrawTokens(amount, penaltyAmount, burnAmount);
  }

  // Withdraw individual earnings
  function individualEarlyExit(uint256 unlockTime) external override {
    require(unlockTime > block.timestamp, "!unlockTime");
    (uint256 amount, uint256 penaltyAmount, uint256 burnAmount, uint256 index) = _ieeWithdrawableBalances(
      msg.sender,
      unlockTime
    );

    if (index >= userEarnings[msg.sender].length) {
      return;
    }

    for (uint256 i = index + 1; i < userEarnings[msg.sender].length; i++) {
      userEarnings[msg.sender][i - 1] = userEarnings[msg.sender][i];
    }
    userEarnings[msg.sender].pop();

    Balances storage bal = balances[msg.sender];
    bal.total = bal.total.sub(amount).sub(penaltyAmount);
    bal.earned = bal.earned.sub(amount).sub(penaltyAmount);

    _withdrawTokens(amount, penaltyAmount, burnAmount);
  }

  // Withdraw full unlocked balance and earnings
  function exit() external override {
    (uint256 amount, uint256 penaltyAmount, uint256 burnAmount) = withdrawableBalance(msg.sender);

    delete userEarnings[msg.sender];

    Balances storage bal = balances[msg.sender];
    bal.total = bal.total.sub(bal.unlocked).sub(bal.earned);
    bal.unlocked = 0;
    bal.earned = 0;

    _withdrawTokens(amount, penaltyAmount, burnAmount);
  }

  function exitLock(uint256 lockDuration) external nonReentrant {
    require(lockDuration.add(block.timestamp) > _maxUnlockTime(msg.sender), "invalid lock duration");

    Balances storage bal = balances[msg.sender];
    _instantLock(msg.sender, bal.total, lockDuration);

    delete userEarnings[msg.sender];

    bal.total = 0;
    bal.unlocked = 0;
    bal.earned = 0;
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _withdrawTokens(uint256 amount, uint256 penaltyAmount, uint256 burnAmount) internal {
    LAB.safeTransfer(msg.sender, amount);
    if (penaltyAmount > 0) {
      if (burnAmount > 0) {
        LAB.safeTransfer(DEAD, burnAmount);
      }
      LAB.safeTransfer(treasury, penaltyAmount.sub(burnAmount));
    }
  }

  function _instantLock(address user, uint256 amount, uint256 lockDuration) private {
    xLAB.lockBehalf(user, amount, lockDuration);
  }

  function _ieeWithdrawableBalances(
    address user,
    uint256 unlockTime
  ) internal view returns (uint256 amount, uint256 penaltyAmount, uint256 burnAmount, uint256 index) {
    index = uint256(-1);
    for (uint256 i = 0; i < userEarnings[user].length; i++) {
      if (userEarnings[user][i].unlockTime == unlockTime) {
        (amount, , penaltyAmount, burnAmount) = _penaltyInfo(userEarnings[user][i]);
        index = i;
        break;
      }
    }
  }

  function _penaltyInfo(
    LockedBalance memory earning
  ) internal view returns (uint256 amount, uint256 penaltyFactor, uint256 penaltyAmount, uint256 burnAmount) {
    if (earning.unlockTime > block.timestamp) {
      penaltyFactor = earning.unlockTime.sub(block.timestamp).mul(HALF).div(vestDuration).add(QUART);
    }
    penaltyAmount = earning.amount.mul(penaltyFactor).div(WHOLE);
    burnAmount = penaltyAmount.mul(burn).div(WHOLE);
    amount = earning.amount.sub(penaltyAmount);
  }

  function _maxUnlockTime(address user) public view returns (uint256 maxUnlockTime) {
    LockedBalance[] storage earnings = userEarnings[user];

    for (uint256 i = 0; i < earnings.length; i++) {
      if (maxUnlockTime < earnings[i].unlockTime) {
        maxUnlockTime = earnings[i].unlockTime;
      }
    }
  }

  function _approveLAB(address _spender) private {
    LAB.safeApprove(_spender, uint256(-1));
  }

  function _disapproveLAB(address _spender) private {
    LAB.safeApprove(_spender, 0);
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IRewardController {
    struct LockedBalance {
        uint256 amount;
        uint256 unlockTime;
        uint256 duration;
    }

    struct EarnedBalance {
        uint256 amount;
        uint256 unlockTime;
        uint256 penalty;
    }

    struct Balances {
        uint256 total; // sum of earnings and lockings;
        uint256 unlocked; // LAB token
        uint256 earned; // LAB token
    }

    event RewardPaid(address indexed user, uint256 reward);

    function mint(address user, uint256 amount, bool withPenalty) external;
    function withdraw(uint256 amount) external;
    function individualEarlyExit(uint256 unlockTime) external;
    function exit() external;

    function earnedBalances(address user) external view returns (uint256 total, uint256 unlocked, EarnedBalance[] memory earningsData);
    function withdrawableBalance(address user) external view returns (uint256 amount, uint256 penaltyAmount, uint256 burnAmount);

}

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

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

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

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

File 28 of 68 : LABDistributor.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "../library/SafeToken.sol";
import "../interfaces/IBEP20.sol";
import "../interfaces/ILABDistributor.sol";
import "../interfaces/IxLAB.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/ICore.sol";
import "../interfaces/IPriceCalculator.sol";
import "../interfaces/IRewardController.sol";

contract LABDistributor is ILABDistributor, Ownable, ReentrancyGuard, Pausable {
  using SafeMath for uint256;
  using SafeToken for address;

  /* ========== CONSTANT VARIABLES ========== */

  uint public constant BOOST_PORTION = 150;
  uint public constant BOOST_MAX = 300;
  uint private constant LAUNCH_TIMESTAMP = 1698019200;
  uint256 private constant XLAB_MIN_LOCK_DURATION = 7 days;

  /* ========== STATE VARIABLES ========== */

  address public LAB;
  ICore public core;
  IxLAB public xLAB;
  IPriceCalculator public priceCalculator;
  IRewardController public rewardController;

  bool public initialized;
  uint256 public compoundMinLockDuration;

  mapping(address => Constant.DistributionInfo) public distributions;
  mapping(address => mapping(address => Constant.DistributionAccountInfo)) public accountDistributions;

  /* ========== MODIFIERS ========== */

  modifier updateDistributionOf(address market) {
    Constant.DistributionInfo storage dist = distributions[market];
    if (dist.accruedAt == 0) {
      dist.accruedAt = block.timestamp;
    }

    uint timeElapsed = block.timestamp > dist.accruedAt ? block.timestamp.sub(dist.accruedAt) : 0;
    if (timeElapsed > 0) {
      if (dist.totalBoostedSupply > 0) {
        dist.accPerShareSupply = dist.accPerShareSupply.add(
          dist.supplySpeed.mul(timeElapsed).mul(1e18).div(dist.totalBoostedSupply)
        );
      }

      if (dist.totalBoostedBorrow > 0) {
        dist.accPerShareBorrow = dist.accPerShareBorrow.add(
          dist.borrowSpeed.mul(timeElapsed).mul(1e18).div(dist.totalBoostedBorrow)
        );
      }
    }
    dist.accruedAt = block.timestamp;
    _;
  }

  modifier onlyCore() {
    require(msg.sender == address(core), "LABDistributor: caller is not Core");
    _;
  }

  /* ========== EVENTS ========== */

  constructor() public {}

  function initialize(address _lab, address _core, address _xlab, address _priceCalculator) external onlyOwner {
    require(initialized == false, "already initialized");
    require(_lab != address(0), "LABDistributor: lab address can't be zero");
    require(_core != address(0), "LABDistributor: core address can't be zero");
    require(_xlab != address(0), "LABDistributor: xlab address can't be zero");
    require(_priceCalculator != address(0), "LABDistributor: priceCalculator address can't be zero");
    require(address(xLAB) == address(0), "LABDistributor: xlab already set");
    require(address(core) == address(0), "LABDistributor: core already set");

    LAB = _lab;
    core = ICore(_core);
    xLAB = IxLAB(_xlab);
    priceCalculator = IPriceCalculator(_priceCalculator);
    compoundMinLockDuration = 30 days;
    initialized = true;
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function setDistributionSpeed(
    address qToken,
    uint supplySpeed,
    uint borrowSpeed
  ) external onlyOwner updateDistributionOf(qToken) {
    Constant.DistributionInfo storage dist = distributions[qToken];
    dist.supplySpeed = supplySpeed;
    dist.borrowSpeed = borrowSpeed;
    emit DistributionSpeedUpdated(qToken, supplySpeed, borrowSpeed);
  }

  function setCompoundMinLockDuration(uint256 newMinLockDuration) external onlyOwner {
    require(newMinLockDuration >= XLAB_MIN_LOCK_DURATION, "LABDistributor: invalid newMinLockDuration");
    compoundMinLockDuration = newMinLockDuration;
  }

  function setPriceCalculator(address _priceCalculator) external onlyOwner {
    priceCalculator = IPriceCalculator(_priceCalculator);
  }

  function setXLAB(address _xlab) external onlyOwner {
    xLAB = IxLAB(_xlab);
  }

  function setRewardController(address _rewardController) external onlyOwner {
    rewardController = IRewardController(_rewardController);
  }

  // For reward distribution to different network (such as Klaytn)
  function withdrawReward(address receiver, uint amount) external onlyOwner {
    LAB.safeTransfer(receiver, amount);
  }

  function pause() external override onlyOwner {
    _pause();
  }

  function unpause() external override onlyOwner {
    _unpause();
  }

  function approve(address _spender, uint256 amount) external override onlyOwner returns (bool) {
    LAB.safeApprove(_spender, amount);
    return true;
  }

  /* ========== VIEWS ========== */

  function accuredLAB(address[] calldata markets, address account) external view override returns (uint) {
    uint amount = 0;
    for (uint i = 0; i < markets.length; i++) {
      amount = amount.add(_accruedLAB(markets[i], account));
    }
    return amount;
  }

  function distributionInfoOf(address market) external view override returns (Constant.DistributionInfo memory) {
    return distributions[market];
  }

  function accountDistributionInfoOf(
    address market,
    address account
  ) external view override returns (Constant.DistributionAccountInfo memory) {
    return accountDistributions[market][account];
  }

  function apyDistributionOf(
    address market,
    address account
  ) external view override returns (Constant.DistributionAPY memory) {
    (uint apySupplyLAB, uint apyBorrowLAB) = _calculateMarketDistributionAPY(market);
    (uint apyAccountSupplyLAB, uint apyAccountBorrowLAB) = _calculateAccountDistributionAPY(market, account);
    return Constant.DistributionAPY(apySupplyLAB, apyBorrowLAB, apyAccountSupplyLAB, apyAccountBorrowLAB);
  }

  function boostedRatioOf(
    address market,
    address account
  ) external view override returns (uint boostedSupplyRatio, uint boostedBorrowRatio) {
    uint accountSupply = ILToken(market).balanceOf(account);
    uint accountBorrow = ILToken(market).borrowBalanceOf(account).mul(1e18).div(ILToken(market).getAccInterestIndex());

    boostedSupplyRatio = accountSupply > 0
      ? accountDistributions[market][account].boostedSupply.mul(1e18).div(accountSupply)
      : 0;
    boostedBorrowRatio = accountBorrow > 0
      ? accountDistributions[market][account].boostedBorrow.mul(1e18).div(accountBorrow)
      : 0;
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function notifySupplyUpdated(
    address market,
    address user
  ) external override nonReentrant onlyCore updateDistributionOf(market) {
    if (block.timestamp < LAUNCH_TIMESTAMP) return;

    Constant.DistributionInfo storage dist = distributions[market];
    Constant.DistributionAccountInfo storage userInfo = accountDistributions[market][user];

    if (userInfo.boostedSupply > 0) {
      uint accLabPerShare = dist.accPerShareSupply.sub(userInfo.accPerShareSupply);
      userInfo.accuredLAB = userInfo.accuredLAB.add(accLabPerShare.mul(userInfo.boostedSupply).div(1e18));
    }
    userInfo.accPerShareSupply = dist.accPerShareSupply;

    uint boostedSupply = _calculateBoostedSupply(market, user);
    dist.totalBoostedSupply = dist.totalBoostedSupply.add(boostedSupply).sub(userInfo.boostedSupply);
    userInfo.boostedSupply = boostedSupply;
  }

  function notifyBorrowUpdated(
    address market,
    address user
  ) external override nonReentrant onlyCore updateDistributionOf(market) {
    if (block.timestamp < LAUNCH_TIMESTAMP) return;

    Constant.DistributionInfo storage dist = distributions[market];
    Constant.DistributionAccountInfo storage userInfo = accountDistributions[market][user];

    if (userInfo.boostedBorrow > 0) {
      uint accLabPerShare = dist.accPerShareBorrow.sub(userInfo.accPerShareBorrow);
      userInfo.accuredLAB = userInfo.accuredLAB.add(accLabPerShare.mul(userInfo.boostedBorrow).div(1e18));
    }
    userInfo.accPerShareBorrow = dist.accPerShareBorrow;

    uint boostedBorrow = _calculateBoostedBorrow(market, user);
    dist.totalBoostedBorrow = dist.totalBoostedBorrow.add(boostedBorrow).sub(userInfo.boostedBorrow);
    userInfo.boostedBorrow = boostedBorrow;
  }

  function notifyTransferred(
    address qToken,
    address sender,
    address receiver
  ) external override nonReentrant onlyCore updateDistributionOf(qToken) {
    if (block.timestamp < LAUNCH_TIMESTAMP) return;

    require(sender != receiver, "QDistributor: invalid transfer");
    Constant.DistributionInfo storage dist = distributions[qToken];
    Constant.DistributionAccountInfo storage senderInfo = accountDistributions[qToken][sender];
    Constant.DistributionAccountInfo storage receiverInfo = accountDistributions[qToken][receiver];

    if (senderInfo.boostedSupply > 0) {
      uint accLabPerShare = dist.accPerShareSupply.sub(senderInfo.accPerShareSupply);
      senderInfo.accuredLAB = senderInfo.accuredLAB.add(accLabPerShare.mul(senderInfo.boostedSupply).div(1e18));
    }
    senderInfo.accPerShareSupply = dist.accPerShareSupply;

    if (receiverInfo.boostedSupply > 0) {
      uint accLabPerShare = dist.accPerShareSupply.sub(receiverInfo.accPerShareSupply);
      receiverInfo.accuredLAB = receiverInfo.accuredLAB.add(accLabPerShare.mul(receiverInfo.boostedSupply).div(1e18));
    }
    receiverInfo.accPerShareSupply = dist.accPerShareSupply;

    uint boostedSenderSupply = _calculateBoostedSupply(qToken, sender);
    uint boostedReceiverSupply = _calculateBoostedSupply(qToken, receiver);
    dist.totalBoostedSupply = dist
      .totalBoostedSupply
      .add(boostedSenderSupply)
      .add(boostedReceiverSupply)
      .sub(senderInfo.boostedSupply)
      .sub(receiverInfo.boostedSupply);
    senderInfo.boostedSupply = boostedSenderSupply;
    receiverInfo.boostedSupply = boostedReceiverSupply;
  }

  function claim(address[] calldata markets, address account) external override onlyCore whenNotPaused {
    uint256 amount = 0;
    uint256 userScore = IBEP20(address(xLAB)).balanceOf(account);
    uint256 totalScore = IBEP20(address(xLAB)).totalSupply();

    for (uint256 i = 0; i < markets.length; i++) {
      amount = amount.add(_claimLab(markets[i], account, userScore, totalScore));
    }

    amount = Math.min(amount, IBEP20(LAB).balanceOf(address(this)));
    LAB.safeTransfer(address(rewardController), amount);
    rewardController.mint(account, amount, true);
    emit Claimed(account, amount);
  }

  function claimBehalf(
    address[] calldata markets,
    address[] calldata accounts
  ) external override onlyCore whenNotPaused {
    for (uint256 i = 0; i < accounts.length; i++) {
      uint256 amount = 0;
      uint256 userScore = IBEP20(address(xLAB)).balanceOf(accounts[i]);
      uint256 totalScore = IBEP20(address(xLAB)).totalSupply();

      for (uint256 j = 0; j < markets.length; j++) {
        amount = amount.add(_claimLab(markets[j], accounts[i], userScore, totalScore));
      }

      amount = Math.min(amount, IBEP20(LAB).balanceOf(address(this)));
      LAB.safeTransfer(address(rewardController), amount);
      rewardController.mint(accounts[i], amount, true);
      emit Claimed(accounts[i], amount);
    }
  }

  function updateAccountBoostedInfo(address user) external override {
    require(user != address(0), "LABDistributor: User account can't be zero address");
    _updateAccountBoostedInfo(user);
  }

  function compound(address[] calldata markets, address account, uint256 lockDuration) external override onlyCore {
    require(account != address(0), "LABDistributor: compound: User account can't be zero address");
    require(lockDuration >= compoundMinLockDuration, "LABDistributor: compound: Lock duration is too short.");

    _compound(markets, account, lockDuration);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _accruedLAB(address market, address user) private view returns (uint) {
    Constant.DistributionInfo memory dist = distributions[market];
    Constant.DistributionAccountInfo memory userInfo = accountDistributions[market][user];

    uint amount = userInfo.accuredLAB;
    uint accPerShareSupply = dist.accPerShareSupply;
    uint accPerShareBorrow = dist.accPerShareBorrow;

    uint timeElapsed = block.timestamp > dist.accruedAt ? block.timestamp.sub(dist.accruedAt) : 0;
    if (
      timeElapsed > 0 ||
      (accPerShareSupply != userInfo.accPerShareSupply) ||
      (accPerShareBorrow != userInfo.accPerShareBorrow)
    ) {
      if (dist.totalBoostedSupply > 0) {
        accPerShareSupply = accPerShareSupply.add(
          dist.supplySpeed.mul(timeElapsed).mul(1e18).div(dist.totalBoostedSupply)
        );

        uint pendingLab = userInfo.boostedSupply.mul(accPerShareSupply.sub(userInfo.accPerShareSupply)).div(1e18);
        amount = amount.add(pendingLab);
      }

      if (dist.totalBoostedBorrow > 0) {
        accPerShareBorrow = accPerShareBorrow.add(
          dist.borrowSpeed.mul(timeElapsed).mul(1e18).div(dist.totalBoostedBorrow)
        );

        uint pendingLab = userInfo.boostedBorrow.mul(accPerShareBorrow.sub(userInfo.accPerShareBorrow)).div(1e18);
        amount = amount.add(pendingLab);
      }
    }
    return amount;
  }

  function _claimLab(address market, address user, uint userScore, uint totalScore) private returns (uint amount) {
    Constant.DistributionAccountInfo storage userInfo = accountDistributions[market][user];

    if (userInfo.boostedSupply > 0) _updateSupplyOf(market, user, userScore, totalScore);
    if (userInfo.boostedBorrow > 0) _updateBorrowOf(market, user, userScore, totalScore);

    amount = amount.add(userInfo.accuredLAB);
    userInfo.accuredLAB = 0;

    return amount;
  }

  function _calculateMarketDistributionAPY(address market) private view returns (uint apySupplyLAB, uint apyBorrowLAB) {
    uint256 decimals = _getDecimals(market);
    // base supply LAB APY == average supply LAB APY * (Total balance / total Boosted balance)
    // base supply LAB APY == (LabRate * 365 days * price Of Lab) / (Total balance * exchangeRate * price of asset) * (Total balance / Total Boosted balance)
    // base supply LAB APY == (LabRate * 365 days * price Of Lab) / (Total boosted balance * exchangeRate * price of asset)
    {
      uint numerSupply = distributions[market].supplySpeed.mul(365 days).mul(priceCalculator.priceOf(LAB));
      uint denomSupply = distributions[market]
        .totalBoostedSupply
        .mul(10 ** (18 - decimals))
        .mul(ILToken(market).exchangeRate())
        .mul(priceCalculator.getUnderlyingPrice(market))
        .div(1e36);
      apySupplyLAB = denomSupply > 0 ? numerSupply.div(denomSupply) : 0;
    }

    // base borrow LAB APY == average borrow LAB APY * (Total balance / total Boosted balance)
    // base borrow LAB APY == (LabRate * 365 days * price Of Lab) / (Total balance * exchangeRate * price of asset) * (Total balance / Total Boosted balance)
    // base borrow LAB APY == (LabRate * 365 days * price Of Lab) / (Total boosted balance * exchangeRate * price of asset)
    {
      uint numerBorrow = distributions[market].borrowSpeed.mul(365 days).mul(priceCalculator.priceOf(LAB));
      uint denomBorrow = distributions[market]
        .totalBoostedBorrow
        .mul(10 ** (18 - decimals))
        .mul(priceCalculator.getUnderlyingPrice(market))
        .div(ILToken(market).getAccInterestIndex());
      apyBorrowLAB = denomBorrow > 0 ? numerBorrow.div(denomBorrow) : 0;
    }
  }

  function _calculateAccountDistributionAPY(
    address market,
    address account
  ) private view returns (uint apyAccountSupplyLAB, uint apyAccountBorrowLAB) {
    if (account == address(0)) return (0, 0);
    (uint apySupplyLAB, uint apyBorrowLAB) = _calculateMarketDistributionAPY(market);

    // user supply LAB APY == ((LabRate * 365 days * price Of Lab) / (Total boosted balance * exchangeRate * price of asset) ) * my boosted balance  / my balance
    uint accountSupply = ILToken(market).balanceOf(account);
    apyAccountSupplyLAB = accountSupply > 0
      ? apySupplyLAB.mul(accountDistributions[market][account].boostedSupply).div(accountSupply)
      : 0;

    // user borrow LAB APY == (LabRate * 365 days * price Of Lab) / (Total boosted balance * interestIndex * price of asset) * my boosted balance  / my balance
    uint accountBorrow = ILToken(market).borrowBalanceOf(account).mul(1e18).div(ILToken(market).getAccInterestIndex());
    apyAccountBorrowLAB = accountBorrow > 0
      ? apyBorrowLAB.mul(accountDistributions[market][account].boostedBorrow).div(accountBorrow)
      : 0;
  }

  function _calculateBoostedSupply(address market, address user) private view returns (uint) {
    uint defaultSupply = ILToken(market).balanceOf(user);
    uint boostedSupply = defaultSupply;

    uint userScore = IBEP20(address(xLAB)).balanceOf(user);
    uint totalScore = IBEP20(address(xLAB)).totalSupply();
    if (userScore > 0 && totalScore > 0) {
      uint scoreBoosted = ILToken(market).totalSupply().mul(userScore).div(totalScore).mul(BOOST_PORTION).div(100);
      boostedSupply = boostedSupply.add(scoreBoosted);
    }
    return Math.min(boostedSupply, defaultSupply.mul(BOOST_MAX).div(100));
  }

  function _calculateBoostedBorrow(address market, address user) private view returns (uint) {
    uint accInterestIndex = ILToken(market).getAccInterestIndex();
    uint defaultBorrow = ILToken(market).borrowBalanceOf(user).mul(1e18).div(accInterestIndex);
    uint boostedBorrow = defaultBorrow;

    uint userScore = IBEP20(address(xLAB)).balanceOf(user);
    uint totalScore = IBEP20(address(xLAB)).totalSupply();
    if (userScore > 0 && totalScore > 0) {
      uint totalBorrow = ILToken(market).totalBorrow().mul(1e18).div(accInterestIndex);
      uint scoreBoosted = totalBorrow.mul(userScore).div(totalScore).mul(BOOST_PORTION).div(100);
      boostedBorrow = boostedBorrow.add(scoreBoosted);
    }
    return Math.min(boostedBorrow, defaultBorrow.mul(BOOST_MAX).div(100));
  }

  function _calculateBoostedSupply(
    address market,
    address user,
    uint userScore,
    uint totalScore
  ) private view returns (uint) {
    uint defaultSupply = ILToken(market).balanceOf(user);
    uint boostedSupply = defaultSupply;

    if (userScore > 0 && totalScore > 0) {
      uint scoreBoosted = ILToken(market).totalSupply().mul(userScore).div(totalScore).mul(BOOST_PORTION).div(100);
      boostedSupply = boostedSupply.add(scoreBoosted);
    }
    return Math.min(boostedSupply, defaultSupply.mul(BOOST_MAX).div(100));
  }

  function _calculateBoostedBorrow(
    address market,
    address user,
    uint userScore,
    uint totalScore
  ) private view returns (uint) {
    uint accInterestIndex = ILToken(market).getAccInterestIndex();
    uint defaultBorrow = ILToken(market).borrowBalanceOf(user).mul(1e18).div(accInterestIndex);
    uint boostedBorrow = defaultBorrow;

    if (userScore > 0 && totalScore > 0) {
      uint totalBorrow = ILToken(market).totalBorrow().mul(1e18).div(accInterestIndex);
      uint scoreBoosted = totalBorrow.mul(userScore).div(totalScore).mul(BOOST_PORTION).div(100);
      boostedBorrow = boostedBorrow.add(scoreBoosted);
    }
    return Math.min(boostedBorrow, defaultBorrow.mul(BOOST_MAX).div(100));
  }

  function _updateSupplyOf(
    address market,
    address user,
    uint userScore,
    uint totalScore
  ) private updateDistributionOf(market) {
    Constant.DistributionInfo storage dist = distributions[market];
    Constant.DistributionAccountInfo storage userInfo = accountDistributions[market][user];

    if (userInfo.boostedSupply > 0) {
      uint accLabPerShare = dist.accPerShareSupply.sub(userInfo.accPerShareSupply);
      userInfo.accuredLAB = userInfo.accuredLAB.add(accLabPerShare.mul(userInfo.boostedSupply).div(1e18));
    }
    userInfo.accPerShareSupply = dist.accPerShareSupply;

    uint boostedSupply = _calculateBoostedSupply(market, user, userScore, totalScore);
    dist.totalBoostedSupply = dist.totalBoostedSupply.add(boostedSupply).sub(userInfo.boostedSupply);
    userInfo.boostedSupply = boostedSupply;
  }

  function _updateBorrowOf(
    address market,
    address user,
    uint userScore,
    uint totalScore
  ) private updateDistributionOf(market) {
    Constant.DistributionInfo storage dist = distributions[market];
    Constant.DistributionAccountInfo storage userInfo = accountDistributions[market][user];

    if (userInfo.boostedBorrow > 0) {
      uint accLabPerShare = dist.accPerShareBorrow.sub(userInfo.accPerShareBorrow);
      userInfo.accuredLAB = userInfo.accuredLAB.add(accLabPerShare.mul(userInfo.boostedBorrow).div(1e18));
    }
    userInfo.accPerShareBorrow = dist.accPerShareBorrow;

    uint boostedBorrow = _calculateBoostedBorrow(market, user, userScore, totalScore);
    dist.totalBoostedBorrow = dist.totalBoostedBorrow.add(boostedBorrow).sub(userInfo.boostedBorrow);
    userInfo.boostedBorrow = boostedBorrow;
  }

  function _updateAccountBoostedInfo(address user) private {
    if (block.timestamp < LAUNCH_TIMESTAMP) return;

    uint userScore = IBEP20(address(xLAB)).balanceOf(user);
    uint totalScore = IBEP20(address(xLAB)).totalSupply();

    address[] memory markets = core.allMarkets();
    for (uint256 i = 0; i < markets.length; i++) {
      address market = markets[i];
      Constant.DistributionAccountInfo memory userInfo = accountDistributions[market][user];
      if (userInfo.boostedSupply > 0) _updateSupplyOf(market, user, userScore, totalScore);
      if (userInfo.boostedBorrow > 0) _updateBorrowOf(market, user, userScore, totalScore);
    }
  }

  function _getDecimals(address lToken) internal view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18;
    } else {
      decimals = IBEP20(underlying).decimals();
    }
  }

  function _compound(address[] calldata markets, address account, uint256 lockDuration) private {
    uint256 amount = 0;
    uint userScore = IBEP20(address(xLAB)).balanceOf(account);
    uint totalScore = IBEP20(address(xLAB)).totalSupply();

    for (uint256 i = 0; i < markets.length; i++) {
      amount = amount.add(_claimLab(markets[i], account, userScore, totalScore));
    }
    amount = Math.min(amount, IBEP20(LAB).balanceOf(address(this)));
    xLAB.lockBehalf(account, amount, lockDuration);
    emit Compound(account, amount);
  }
}

// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.4.0;

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

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

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

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

  /**
   * @dev Returns the bep token owner.
   */
  function getOwner() external view returns (address);

  /**
   * @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);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

import "../library/SafeToken.sol";
import "../library/Constant.sol";

import "../interfaces/IRebateDistributor.sol";
import "../interfaces/IPriceCalculator.sol";
import "../interfaces/ICore.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/IxLAB.sol";
import "../interfaces/IBEP20.sol";

contract RebateDistributor is IRebateDistributor, Ownable, ReentrancyGuard, Pausable {
  using SafeMath for uint256;
  using SafeToken for address;

  /* ========== CONSTANT VARIABLES ========== */

  uint256 public constant MAX_ADMIN_FEE_RATE = 5e17;
  uint256 public constant REBATE_CYCLE = 1 weeks;
  address internal constant ETH = 0x0000000000000000000000000000000000000000;

  /* ========== STATE VARIABLES ========== */

  ICore public core;
  IxLAB public xLAB;
  IPriceCalculator public priceCalc;
  address public LAB;

  Constant.RebateCheckpoint[] public rebateCheckpoints;
  address public keeper;
  uint256 public adminFeeRate;
  uint256 public weeklyLabSpeed;

  mapping(address => uint256) private userCheckpoint;
  uint256 private adminCheckpoint;

  // initializer
  bool public initialized;

  /* ========== MODIFIERS ========== */

  /// @dev msg.sender 가 core address 인지 검증
  modifier onlyCore() {
    require(msg.sender == address(core), "RebateDistributor: only core contract");
    _;
  }

  modifier onlyKeeper() {
    require(msg.sender == keeper || msg.sender == owner(), "RebateDistributor: caller is not the owner or keeper");
    _;
  }

  /* ========== EVENTS ========== */

  event RebateClaimed(address indexed user, uint256[] marketFees, uint256 totalLabAmount);
  event AdminFeeRateUpdated(uint256 newAdminFeeRate);
  event AdminRebateTreasuryUpdated(address newTreasury);
  event KeeperUpdated(address newKeeper);
  event WeeklyLabSpeedUpdated(uint256 newWeeklyLabSpeed);

  /* ========== SPECIAL FUNCTIONS ========== */

  receive() external payable {}

  /* ========== INITIALIZER ========== */

  constructor() public {}

  function initialize(
    address _core,
    address _xlab,
    address _priceCalc,
    address _lab,
    uint256 _weeklyLabSpeed
  ) external onlyOwner {
    require(initialized == false, "RebateDistributor: already initialized");
    require(_core != address(0), "RebateDistributor: invalid core address");
    require(_xlab != address(0), "RebateDistributor: invalid xlab address");
    require(_priceCalc != address(0), "RebateDistributor: invalid priceCalc address");

    core = ICore(_core);
    xLAB = IxLAB(_xlab);
    priceCalc = IPriceCalculator(_priceCalc);
    LAB = _lab;

    adminCheckpoint = block.timestamp;
    adminFeeRate = 0;
    weeklyLabSpeed = _weeklyLabSpeed;

    if (rebateCheckpoints.length == 0) {
      rebateCheckpoints.push(
        Constant.RebateCheckpoint({
          timestamp: _truncateTimestamp(block.timestamp),
          totalScore: IBEP20(address(xLAB)).totalSupply(),
          adminFeeRate: adminFeeRate,
          weeklyLabSpeed: _weeklyLabSpeed,
          additionalLabAmount: 0
        })
      );
    }

    initialized = true;
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function pause() external override onlyOwner {
    _pause();
  }

  function unpause() external override onlyOwner {
    _unpause();
  }

  function setPriceCalculator(address _priceCalculator) external onlyOwner {
    require(_priceCalculator != address(0), "RebateDistributor: invalid priceCalculator address");
    priceCalc = IPriceCalculator(_priceCalculator);
  }

  function setXLAB(address _xlab) external onlyOwner {
    require(_xlab != address(0), "RebateDistributor: invalid xLAB address");
    xLAB = IxLAB(_xlab);
  }

  /// @notice set keeper address
  /// @param _keeper new keeper address
  function setKeeper(address _keeper) external override onlyKeeper {
    require(_keeper != address(0), "RebateDistributor: invalid keeper address");
    keeper = _keeper;
    emit KeeperUpdated(_keeper);
  }

  function updateAdminFeeRate(uint256 newAdminFeeRate) external override onlyKeeper {
    require(newAdminFeeRate <= MAX_ADMIN_FEE_RATE, "RebateDisbtirubor: Invalid fee rate");
    adminFeeRate = newAdminFeeRate;
    emit AdminFeeRateUpdated(newAdminFeeRate);
  }

  function updateWeeklyLabSpeed(uint256 newWeeklyLabSpeed) external onlyKeeper {
    weeklyLabSpeed = newWeeklyLabSpeed;
    emit WeeklyLabSpeedUpdated(newWeeklyLabSpeed);
  }

  /// @notice Claim accured admin rebates
  function claimAdminRebates()
    external
    override
    nonReentrant
    onlyKeeper
    returns (uint256 addtionalLabAmount, uint256[] memory marketFees)
  {
    (addtionalLabAmount, marketFees) = accruedAdminRebate();
    Constant.RebateCheckpoint memory lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    adminCheckpoint = _truncateTimestamp(lastCheckpoint.timestamp.sub(REBATE_CYCLE));

    address(LAB).safeTransfer(msg.sender, addtionalLabAmount);
  }

  function withdrawReward(address receiver, uint amount) external onlyOwner {
    LAB.safeTransfer(receiver, amount);
  }

  /* ========== VIEWS ========== */

  /// @notice Accured rebate amount of account
  /// @param account account address
  function accruedRebates(
    address account
  ) public view override returns (uint256 labAmount, uint256 additionalLabAmount, uint256[] memory marketFees) {
    Constant.RebateCheckpoint memory lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    address[] memory markets = core.allMarkets();
    marketFees = new uint256[](markets.length);

    if (xLAB.balanceHistoryOf(account).length == 0) return (labAmount, additionalLabAmount, marketFees);

    for (
      uint256 nextTimestamp = _truncateTimestamp(
        userCheckpoint[account] != 0 ? userCheckpoint[account] : xLAB.balanceHistoryOf(account)[0].timestamp
      ).add(REBATE_CYCLE);
      nextTimestamp <= lastCheckpoint.timestamp.sub(REBATE_CYCLE);
      nextTimestamp = nextTimestamp.add(REBATE_CYCLE)
    ) {
      uint256 votingPower = _getUserVPAt(account, nextTimestamp);
      if (votingPower == 0) continue;

      Constant.RebateCheckpoint storage currentCheckpoint = rebateCheckpoints[_getCheckpointIdxAt(nextTimestamp)];
      labAmount = labAmount.add(currentCheckpoint.weeklyLabSpeed.mul(votingPower).div(1e18));
      additionalLabAmount = additionalLabAmount.add(
        currentCheckpoint
          .additionalLabAmount
          .mul(uint256(1e18).sub(currentCheckpoint.adminFeeRate).mul(votingPower))
          .div(1e36)
      );

      for (uint256 i = 0; i < markets.length; i++) {
        if (currentCheckpoint.marketFees[markets[i]] > 0) {
          uint256 marketFee = currentCheckpoint
            .marketFees[markets[i]]
            .mul(uint256(1e18).sub(currentCheckpoint.adminFeeRate).mul(votingPower))
            .div(1e36);
          marketFees[i] = marketFees[i].add(marketFee);
        }
      }
    }
  }

  /// @notice Accrued rebate amount of admin
  function accruedAdminRebate() public view returns (uint256 additionalLabAmount, uint256[] memory marketFees) {
    Constant.RebateCheckpoint memory lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    address[] memory markets = core.allMarkets();
    marketFees = new uint256[](markets.length);

    for (
      uint256 nextTimestamp = _truncateTimestamp(adminCheckpoint).add(REBATE_CYCLE);
      nextTimestamp <= lastCheckpoint.timestamp.sub(REBATE_CYCLE);
      nextTimestamp = nextTimestamp.add(REBATE_CYCLE)
    ) {
      uint256 checkpointIdx = _getCheckpointIdxAt(nextTimestamp);
      Constant.RebateCheckpoint storage currentCheckpoint = rebateCheckpoints[checkpointIdx];
      additionalLabAmount = additionalLabAmount.add(
        currentCheckpoint.additionalLabAmount.mul(currentCheckpoint.adminFeeRate).div(1e18)
      );

      for (uint256 i = 0; i < markets.length; i++) {
        if (currentCheckpoint.marketFees[markets[i]] > 0) {
          marketFees[i] = marketFees[i].add(
            currentCheckpoint.marketFees[markets[i]].mul(currentCheckpoint.adminFeeRate).div(1e18)
          );
        }
      }
    }
  }

  function totalAccruedRevenue() public view returns (uint256[] memory marketFees, address[] memory markets) {
    Constant.RebateCheckpoint memory lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    markets = core.allMarkets();
    marketFees = new uint256[](markets.length);

    for (
      uint256 nextTimestamp = _truncateTimestamp(rebateCheckpoints[0].timestamp);
      nextTimestamp <= lastCheckpoint.timestamp.sub(REBATE_CYCLE);
      nextTimestamp = nextTimestamp.add(REBATE_CYCLE)
    ) {
      uint256 checkpointIdx = _getCheckpointIdxAt(nextTimestamp);
      Constant.RebateCheckpoint storage currentCheckpoint = rebateCheckpoints[checkpointIdx];
      for (uint256 i = 0; i < markets.length; i++) {
        if (currentCheckpoint.marketFees[markets[i]] > 0) {
          marketFees[i] = marketFees[i].add(currentCheckpoint.marketFees[markets[i]]);
        }
      }
    }
  }

  function weeklyRebatePool() public view override returns (uint256 labAmount) {
    Constant.RebateCheckpoint storage lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    labAmount = labAmount.add(lastCheckpoint.weeklyLabSpeed).add(
      lastCheckpoint.additionalLabAmount.mul(uint256(1e18).sub(lastCheckpoint.adminFeeRate)).div(1e18)
    );
  }

  function weeklyProfitOfVP(uint256 vp) public view override returns (uint256 labAmount) {
    require(vp >= 0 && vp <= 1e18, "RebateDistributor: Invalid VP");
    uint256 weeklyLabAmount = weeklyRebatePool();
    labAmount = weeklyLabAmount.mul(vp).div(1e18);
  }

  function weeklyProfitOf(address account) external view override returns (uint256) {
    uint256 vp = _getUserVPAt(account, block.timestamp.add(REBATE_CYCLE));
    return weeklyProfitOfVP(vp);
  }

  function indicativeAPR() external view override returns (uint256) {
    uint256 totalScore = IBEP20(address(xLAB)).totalSupply();
    if (totalScore == 0) {
      return 0;
    }

    uint256 preScore = xLAB.calcVeAmount(1e18, 365 days);
    uint256 vp = preScore.mul(1e18).div(totalScore);
    uint256 weeklyProfit = weeklyProfitOfVP(vp >= 1e18 ? 1e18 : vp);

    return weeklyProfit.mul(52);
  }

  function indicativeAPROf(uint256 amount, uint256 lockDuration) external view override returns (uint256) {
    uint256 totalScore = IBEP20(address(xLAB)).totalSupply();
    if (totalScore == 0) {
      return 0;
    }

    uint256 preScore = xLAB.calcVeAmount(amount, lockDuration);
    uint256 vp = preScore.mul(1e18).div(totalScore.add(preScore));
    uint256 weeklyProfit = weeklyProfitOfVP(vp >= 1e18 ? 1e18 : vp);

    return weeklyProfit.mul(52).mul(1e18).div(amount);
  }

  function indicativeAPROfUser(address account) external view override returns (uint256) {
    uint256 vp = _getUserVP(account);
    uint256 weeklyProfit = weeklyProfitOfVP(vp >= 1e18 ? 1e18 : vp);
    uint256 lockedBalance = xLAB.lockedBalanceOf(account);

    if (vp == 0 || lockedBalance == 0) return 0;

    return weeklyProfit.mul(1e18).mul(52).div(lockedBalance);
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  /// @notice Add checkpoint if needed and supply supluses
  function checkpoint() external override onlyKeeper nonReentrant {
    Constant.RebateCheckpoint memory lastRebateScore = rebateCheckpoints[rebateCheckpoints.length - 1];
    address[] memory markets = core.allMarkets();

    uint256 nextTimestamp = lastRebateScore.timestamp.add(REBATE_CYCLE);
    while (block.timestamp >= nextTimestamp) {
      rebateCheckpoints.push(
        Constant.RebateCheckpoint({
          totalScore: IBEP20(address(xLAB)).totalSupply(),
          timestamp: nextTimestamp,
          adminFeeRate: adminFeeRate,
          weeklyLabSpeed: weeklyLabSpeed,
          additionalLabAmount: 0
        })
      );
      nextTimestamp = nextTimestamp.add(REBATE_CYCLE);

      for (uint256 i = 0; i < markets.length; i++) {
        ILToken(markets[i]).withdrawReserves();
        address underlying = ILToken(markets[i]).underlying();

        if (underlying == address(ETH)) {
          SafeToken.safeTransferETH(msg.sender, address(this).balance);
        } else {
          underlying.safeTransfer(msg.sender, SafeToken.myBalance(underlying));
        }
      }
    }
  }

  function addLABToRebatePool(uint256 amount) external override nonReentrant {
    Constant.RebateCheckpoint storage lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    lastCheckpoint.additionalLabAmount = lastCheckpoint.additionalLabAmount.add(amount);
    address(LAB).safeTransferFrom(msg.sender, address(this), amount);
  }

  function addMarketUTokenToRebatePool(address lToken, uint256 uAmount) external payable override nonReentrant {
    Constant.RebateCheckpoint storage lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    address underlying = ILToken(lToken).underlying();

    if (underlying == ETH && msg.value > 0) {
      lastCheckpoint.marketFees[lToken] = lastCheckpoint.marketFees[lToken].add(msg.value);
    } else if (underlying != ETH) {
      address(underlying).safeTransferFrom(msg.sender, address(this), uAmount);
      lastCheckpoint.marketFees[lToken] = lastCheckpoint.marketFees[lToken].add(uAmount);
    }
  }

  /// @notice Claim accured all rebates
  function claimRebates()
    external
    override
    nonReentrant
    whenNotPaused
    returns (uint256 labAmount, uint256 additionalLabAmount, uint256[] memory marketFees)
  {
    (labAmount, additionalLabAmount, marketFees) = accruedRebates(msg.sender);
    Constant.RebateCheckpoint memory lastCheckpoint = rebateCheckpoints[rebateCheckpoints.length - 1];
    userCheckpoint[msg.sender] = _truncateTimestamp(lastCheckpoint.timestamp.sub(REBATE_CYCLE));

    address(LAB).safeTransfer(msg.sender, labAmount.add(additionalLabAmount));

    emit RebateClaimed(msg.sender, marketFees, labAmount.add(additionalLabAmount));
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  /// @notice Find checkpoint index of timestamp
  /// @param timestamp checkpoint timestamp
  function _getCheckpointIdxAt(uint256 timestamp) private view returns (uint256) {
    timestamp = _truncateTimestamp(timestamp);

    for (uint256 i = rebateCheckpoints.length - 1; i < uint256(-1); i--) {
      if (rebateCheckpoints[i].timestamp == timestamp) {
        return i;
      }
    }

    revert("RebateDistributor: checkpoint index error");
  }

  function _getUserVP(address account) private view returns (uint256) {
    uint256 userScore = IBEP20(address(xLAB)).balanceOf(account);
    uint256 totalScore = IBEP20(address(xLAB)).totalSupply();

    return totalScore != 0 ? userScore.mul(1e18).div(totalScore) : 0;
  }

  /// @notice Get user voting power at timestamp
  /// @param account account address
  /// @param timestamp timestamp
  function _getUserVPAt(address account, uint256 timestamp) private view returns (uint256) {
    timestamp = _truncateTimestamp(timestamp);
    uint256 userScore = xLAB.balanceOfAt(account, timestamp);
    uint256 idx = _getCheckpointIdxAt(timestamp);
    uint256 totalScore = rebateCheckpoints[idx].totalScore;

    return totalScore != 0 ? userScore.mul(1e18).div(totalScore).div(1e8).mul(1e8) : 0;
  }

  /// @notice Truncate timestamp to adjust to rebate checkpoint
  function _truncateTimestamp(uint256 timestamp) private pure returns (uint256) {
    return timestamp.div(REBATE_CYCLE).mul(REBATE_CYCLE);
  }

  function _getDecimals(address lToken) private view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18;
    } else {
      decimals = IBEP20(underlying).decimals();
    }
  }
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "../interfaces/ISaleLabOverflowFarm.sol";
import "../interfaces/IBEP20.sol";

import "../library/SafeToken.sol";

contract SaleLabOverflowFarm is Ownable, ReentrancyGuard, Pausable, ISaleLabOverflowFarm {
    using SafeMath for uint256;
    using SafeToken for address;

    // The offering token
    IBEP20 public offeringToken;
    // The startTime when IDO starts
    uint256 public override startTime;
    // The endTime when IDO ends
    uint256 public override endTime;
    // total amount of raising tokens need to be raised
    uint256 public override raisingAmount;
    // total amount of offering tokens that will offer
    uint256 public override offeringAmount;
    // total amount of raising tokens that have already raised
    uint256 public override totalAmount;
    // hardcap
    // address => amount
    mapping(address => UserInfo) public override userInfo;
    // participators
    address[] public addressList;

    // initializer
    bool public initialized;

    // OVERFLOW FARMING
    // The timestamp of the last pool update
    uint256 public lastRewardTimestamp;

    // Accrued token per share
    uint256 public accTokenPerShare;

    uint256 public startReleaseTimestamp;
    uint256 public endReleaseTimestamp;
    uint256 public override harvestTimestamp;

    mapping(address => uint256) public lastUnlockTimestamp;
    mapping(address => uint256) public claimed;

    // Reward tokens created per second.
    uint256 public override rewardPerSecond;
    // Reward tokens
    IBEP20 public rewardToken;

    mapping(address => uint256) public rewardStored;

    mapping(address => bool) public override whitelist;

    constructor() public {}

    function initialize(
        IBEP20 _offeringToken,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _offeringAmount,
        uint256 _raisingAmount,
        IBEP20 _rewardToken,
        uint256 _rewardPerSecond,
        uint256 _startReleaseTimestamp,
        uint256 _endReleaseTimestamp,
        uint256 _harvestTimestamp
    ) external onlyOwner {
        require(initialized == false, "already initialized");

        offeringToken = _offeringToken;
        startTime = _startTime;
        endTime = _endTime;
        offeringAmount = _offeringAmount;
        raisingAmount = _raisingAmount;
        totalAmount = 0;

        rewardToken = _rewardToken;
        rewardPerSecond = _rewardPerSecond;

        lastRewardTimestamp = startTime;

        startReleaseTimestamp = _startReleaseTimestamp;
        endReleaseTimestamp = _endReleaseTimestamp;
        harvestTimestamp = _harvestTimestamp;

        initialized = true;
    }

    function setOfferingAmount(uint256 _offerAmount) external onlyOwner {
        require(block.timestamp < startTime, "no");
        offeringAmount = _offerAmount;
    }

    function setRaisingAmount(uint256 _raisingAmount) external onlyOwner {
        require(block.timestamp < startTime, "no");
        raisingAmount = _raisingAmount;
    }

    function setWhitelist(address _addr, bool isWhiteUser) external onlyOwner {
        whitelist[_addr] = isWhiteUser;
    }

    function setWhitelists(address[] calldata _addrs, bool isWhiteUser) external onlyOwner {
        for (uint256 i = 0; i < _addrs.length; i++) {
            whitelist[_addrs[i]] = isWhiteUser;
        }
    }

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

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

    function withdrawRaisingToken(uint256 _amount) external onlyOwner {
        require(block.timestamp > endTime, "not withdraw time");
        require(_amount <= address(this).balance, "not enough token");
        require(_amount <= raisingAmount, "can not withdraw amount greater than raisingAmount");
        _safeTransferETH(msg.sender, _amount);
    }

    function withdrawOfferingToken(uint256 _amount) external onlyOwner {
        require(block.timestamp > endTime, "not withdraw time");
        require(_amount <= offeringToken.balanceOf(address(this)), "not enough token");
        address(offeringToken).safeTransfer(msg.sender, _amount);
    }

    function deposit(address _referral) external payable override nonReentrant {
        require(block.timestamp > startTime && block.timestamp < endTime, "not IDO time");
        require(msg.value > 0, "msg.value must be higher than 0");

        _updatePool();

        uint256 _rewardStore = userInfo[msg.sender].amount.mul(accTokenPerShare).div(1e18).sub(userInfo[msg.sender].rewardDebt);
        rewardStored[msg.sender] = rewardStored[msg.sender].add(_rewardStore);

        if (userInfo[msg.sender].amount == 0) {
            addressList.push(address(msg.sender));
        }
        userInfo[msg.sender].amount = userInfo[msg.sender].amount.add(msg.value);
        totalAmount = totalAmount.add(msg.value);

        userInfo[msg.sender].rewardDebt = userInfo[msg.sender].amount.mul(accTokenPerShare).div(1e18);

        if (lastUnlockTimestamp[msg.sender] < startReleaseTimestamp) {
            lastUnlockTimestamp[msg.sender] = startReleaseTimestamp;
        }
        emit Deposit(msg.sender, msg.value, _referral);
    }

    function harvest() external override nonReentrant whenNotPaused {
        require(block.timestamp > startReleaseTimestamp, "not release time");
        require(userInfo[msg.sender].amount > 0, "have you participated?");
        require(!userInfo[msg.sender].claimed, "nothing to harvest");
        _updatePool();

        uint256 offeringTokenAmount = getOfferingAmount(msg.sender);
        offeringTokenAmount = offeringTokenAmount.mul(3).div(10); // TGE 30%

        uint256 refundingTokenAmount = getRefundingAmount(msg.sender);

        if (refundingTokenAmount > 0) {
            _safeTransferETH(msg.sender, refundingTokenAmount);
        }

        claimed[msg.sender] = claimed[msg.sender].add(offeringTokenAmount);
        address(offeringToken).safeTransfer(msg.sender, offeringTokenAmount);

        userInfo[msg.sender].claimed = true;
        emit Harvest(msg.sender, offeringTokenAmount, refundingTokenAmount);
    }

    function harvestOverflowReward() external override nonReentrant whenNotPaused {
        require(block.timestamp > harvestTimestamp, "not harvest time");
        require(userInfo[msg.sender].amount > 0, "have you participated?");
        UserInfo storage user = userInfo[msg.sender];
        _updatePool();

        uint256 pending = 0;
        if (user.amount > 0) {
            pending = user.amount.mul(accTokenPerShare).div(1e18).sub(user.rewardDebt);
            pending = pending.add(rewardStored[msg.sender]);
            if (pending > 0) {
                address(rewardToken).safeTransfer(msg.sender, pending);
            }
            rewardStored[msg.sender] = 0;
        }
        user.rewardDebt = user.amount.mul(accTokenPerShare).div(1e18);
    }

    function harvestVestingTokens() external override nonReentrant whenNotPaused {
        require(block.timestamp > startReleaseTimestamp, "not release time");
        require(userInfo[msg.sender].amount > 0, "have you participated?");

        _updatePool();

        uint256 _tokensToClaim = tokensClaimable(msg.sender);
        require(_tokensToClaim > 0, "No tokens to claim");
        claimed[msg.sender] = claimed[msg.sender].add(_tokensToClaim);

        address(offeringToken).safeTransfer(msg.sender, _tokensToClaim);
        lastUnlockTimestamp[msg.sender] = block.timestamp;
    }

    function hasHarvest(address _user) external view override returns (bool) {
        return userInfo[_user].claimed;
    }

    function getUserAllocation(address _user) public view override returns (uint256) {
        return userInfo[_user].amount.mul(1e18).div(totalAmount);
    }

    // get the amount of ido token you will get, 30% TGE, 70% Vesting in 7Months
    function getOfferingAmount(address _user) public view override returns (uint256) {
        if (totalAmount > raisingAmount) {
            uint256 allocation = getUserAllocation(_user);
            if (whitelist[_user]) {
                return offeringAmount.mul(allocation).mul(105).div(1e18).div(100);
            } else {
                return offeringAmount.mul(allocation).div(1e18);
            }
        } else {
            if (whitelist[_user]) {
                return userInfo[_user].amount.mul(offeringAmount).mul(105).div(raisingAmount).div(100);
            } else {
                return userInfo[_user].amount.mul(offeringAmount).div(raisingAmount);
            }
        }
    }

    function getRefundingAmount(address _user) public view override returns (uint256) {
        if (totalAmount <= raisingAmount) {
            return 0;
        }
        uint256 allocation = getUserAllocation(_user);
        uint256 payAmount = raisingAmount.mul(allocation).div(1e18);
        return userInfo[_user].amount.sub(payAmount);
    }

    function tokensClaimable(address _user) public view override returns (uint256 claimableAmount) {
        if (userInfo[_user].amount == 0) {
            return 0;
        }
        uint256 unclaimedTokens = offeringToken.balanceOf(address(this));
        claimableAmount = getOfferingAmount(_user);
        claimableAmount = claimableAmount.sub(claimed[_user]);

        if (userInfo[_user].claimed == false) {
            uint256 _offeringTokenAmount = getOfferingAmount(_user);
            _offeringTokenAmount = _offeringTokenAmount.mul(3).div(10); // TGE 30%
            claimableAmount = claimableAmount.sub(_offeringTokenAmount);
        }

        claimableAmount = _canUnlockAmount(_user, claimableAmount);

        if (claimableAmount > unclaimedTokens) {
            claimableAmount = unclaimedTokens;
        }
    }

    function pendingReward(address _user) external view override returns (uint256) {
        UserInfo storage user = userInfo[_user];
        uint256 stakedTokenSupply = totalAmount;
        uint256 reward = 0;
        if (block.timestamp > lastRewardTimestamp && stakedTokenSupply != 0) {
            uint256 multiplier = _getMultiplier(lastRewardTimestamp, block.timestamp);
            uint256 rewardTokenReward = multiplier.mul(rewardPerSecond);
            uint256 _accTokenPerShare = rewardTokenReward.mul(1e18).div(stakedTokenSupply);
            uint256 adjustedTokenPerShare = accTokenPerShare.add(_accTokenPerShare);
            reward = user.amount.mul(adjustedTokenPerShare).div(1e18).sub(user.rewardDebt);
        } else {
            reward = user.amount.mul(accTokenPerShare).div(1e18).sub(user.rewardDebt);
        }
        return reward.add(rewardStored[_user]);
    }

    function getAddressListLength() external view override returns (uint256) {
        return addressList.length;
    }


    function _getMultiplier(
        uint256 _from,
        uint256 _to
    ) internal view returns (uint256) {
        if (_to <= endTime) {
            return _to.sub(_from);
        } else if (_from >= endTime) {
            return 0;
        } else {
            return endTime.sub(_from);
        }
    }

    /*
     * @notice Update reward variables of the given pool to be up-to-date.
     */
    function _updatePool() private {
        if (block.timestamp <= lastRewardTimestamp) {
            return;
        }

        uint256 stakedTokenSupply = totalAmount;

        if (stakedTokenSupply == 0) {
            lastRewardTimestamp = block.timestamp;
            return;
        }

        uint256 multiplier = _getMultiplier(
            lastRewardTimestamp,
            block.timestamp
        );

        uint256 rewardTokenReward = multiplier.mul(rewardPerSecond);
        uint256 _accTokenPerShare = rewardTokenReward.mul(1e18).div(stakedTokenSupply);
        accTokenPerShare = accTokenPerShare.add(_accTokenPerShare);
        lastRewardTimestamp = block.timestamp;
    }

    function _safeTransferETH(address to, uint256 value) private {
        (bool success, ) = to.call{ value: value }(new bytes(0));
        require(success, "!safeTransferETH");
    }

    function _canUnlockAmount(address _user, uint256 _unclaimedTokenAmount) private view returns (uint256) {
        if (block.timestamp < startReleaseTimestamp) {
            return 0;
        } else if (block.timestamp >= endReleaseTimestamp) {
            return _unclaimedTokenAmount;
        } else {
            uint256 releasedTimestamp = block.timestamp.sub(lastUnlockTimestamp[_user]);
            uint256 timeLeft = endReleaseTimestamp.sub(lastUnlockTimestamp[_user]);
            return _unclaimedTokenAmount.mul(releasedTimestamp).div(timeLeft);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface ISaleLabOverflowFarm {
    // Info of each user.
    struct UserInfo {
        uint256 amount; // How many tokens the user has provided.
        bool claimed; // default false
        uint256 rewardDebt;
    }

    event Deposit(address indexed user, uint256 amount, address referral);
    event Harvest(
        address indexed user,
        uint256 offeringAmount,
        uint256 excessAmount
    );

    function deposit(address _referral) external payable;
    function harvest() external;
    function harvestOverflowReward() external;
    function harvestVestingTokens() external;

    function getOfferingAmount(address _user) external view returns (uint256);
    function getRefundingAmount(address _user) external view returns (uint256);
    function getUserAllocation(address _user) external view returns (uint256);
    function hasHarvest(address _user) external view returns (bool);
    function tokensClaimable(address _user) external view returns (uint256 claimableAmount);

    function pendingReward(address _user) external view returns (uint256);
    function getAddressListLength() external view returns (uint256);
    function rewardPerSecond() external view returns (uint256);

    function startTime() external view returns (uint256);
    function endTime() external view returns (uint256);
    function harvestTimestamp() external view returns (uint256);
    function raisingAmount() external view returns (uint256);
    function offeringAmount() external view returns (uint256);
    function totalAmount() external view returns (uint256);

    function userInfo(address user) external view returns (uint256, bool, uint256);
    function whitelist(address user) external view returns (bool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

import "@uniswap/v2-periphery/contracts/interfaces/IERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol";
import "@uniswap/lib/contracts/libraries/TransferHelper.sol";

import "../library/SafeToken.sol";
import "../library/Whitelist.sol";

import "../interfaces/ICore.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/IPriceCalculator.sol";
import "../interfaces/IFlashLoanReceiver.sol";
import "../interfaces/IPool.sol";
import "../interfaces/ISwapRouter.sol";

contract LiquidationV3 is IFlashLoanReceiver, Whitelist, ReentrancyGuard {
  using SafeMath for uint256;
  using SafeToken for address;

  struct FlashLoanParam {
    address lTokenBorrowed;
    address underlyingBorrowed;
    address lTokenCollateral;
    address borrower;
    uint256 amount;
  }

  /* ========== CONSTANTS ============= */

  address private constant ETH = address(0);
  address private constant WETH = address(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
  address private constant WBTC = address(0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f);
  address private constant DAI = address(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);
  address private constant USDT = address(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
  address private constant USDC = address(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);
  address private constant ARB = address(0x912CE59144191C1204E64559FE8253a0e49E6548);

  ISwapRouter private constant router = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
  IPool private constant lendPool = IPool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);

  /* ========== STATE VARIABLES ========== */

  mapping(address => mapping(address => bool)) private tokenApproval;
  mapping(address => mapping(address => uint24)) private pools;
  mapping(address => address) private flashLoanTokens;
  ICore public core;
  IPriceCalculator public priceCalculator;

  receive() external payable {}

  /* ========== Event ========== */

  event Liquidated(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower,
    uint256 amount,
    uint256 rebateAmount
  );

  /* ========== INITIALIZER ========== */

  constructor(address _core, address _priceCalculator) public {
    require(_core != address(0), "Liquidation: core address can't be zero");
    require(_priceCalculator != address(0), "Liquidation: priceCalculator address can't be zero");

    core = ICore(_core);
    priceCalculator = IPriceCalculator(_priceCalculator);

    _approveTokens();
    _addPools();
    _initFlashLoanTokens();
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function liquidate(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower,
    uint256 amount
  ) external onlyWhitelisted nonReentrant {
    (uint256 collateralInUSD, , uint256 borrowInUSD) = core.accountLiquidityOf(borrower);
    require(borrowInUSD > collateralInUSD, "Liquidation: Insufficient shortfall");

    _flashLoan(lTokenBorrowed, lTokenCollateral, borrower, amount);

    address underlying = ILToken(lTokenBorrowed).underlying();

    emit Liquidated(
      lTokenBorrowed,
      lTokenCollateral,
      borrower,
      amount,
      underlying == ETH ? address(this).balance : IERC20(ILToken(lTokenBorrowed).underlying()).balanceOf(address(this))
    );

    _sendTokenToRebateDistributor(underlying);
  }

  /// @notice Liquidate borrower's max value debt using max value collateral
  /// @param borrower borrower account address
  function autoLiquidate(address borrower) external onlyWhitelisted nonReentrant {
    (uint256 collateralInUSD, , uint256 borrowInUSD) = core.accountLiquidityOf(borrower);
    require(borrowInUSD > collateralInUSD, "Liquidation: Insufficient shortfall");

    (address lTokenBorrowed, address lTokenCollateral) = _getTargetMarkets(borrower);
    uint256 liquidateAmount = _getMaxLiquidateAmount(lTokenBorrowed, lTokenCollateral, borrower);
    require(liquidateAmount > 0, "Liquidation: liquidate amount error");

    _flashLoan(lTokenBorrowed, lTokenCollateral, borrower, liquidateAmount);

    address underlying = ILToken(lTokenBorrowed).underlying();

    emit Liquidated(
      lTokenBorrowed,
      lTokenCollateral,
      borrower,
      liquidateAmount,
      underlying == ETH ? address(this).balance : IERC20(ILToken(lTokenBorrowed).underlying()).balanceOf(address(this))
    );

    _sendTokenToRebateDistributor(underlying);
  }

  /* ========== VIEW FUNCTIONS ========== */

  function liquidateInfo(address borrower) external view returns (uint256, uint256, address, address, uint256) {
    (uint256 collateralInUSD, , uint256 borrowInUSD) = core.accountLiquidityOf(borrower);
    require(borrowInUSD > collateralInUSD, "Liquidation: Insufficient shortfall");

    (address lTokenBorrowed, address lTokenCollateral) = _getTargetMarkets(borrower);
    uint256 liquidateAmount = _getMaxLiquidateAmount(lTokenBorrowed, lTokenCollateral, borrower);
    require(liquidateAmount > 0, "Liquidation: liquidate amount error");

    return (collateralInUSD, borrowInUSD, lTokenBorrowed, lTokenCollateral, liquidateAmount);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _approveTokens() private {
    address[] memory markets = core.allMarkets();

    for (uint256 i = 0; i < markets.length; i++) {
      address token = ILToken(markets[i]).underlying();
      _approveToken(token, address(markets[i]));
      _approveToken(token, address(router));
      _approveToken(token, address(lendPool));
    }
    _approveToken(WETH, address(router));
    _approveToken(WETH, address(lendPool));
  }

  function _approveToken(address token, address spender) private {
    if (token != ETH && !tokenApproval[token][spender]) {
      token.safeApprove(spender, uint256(-1));
      tokenApproval[token][spender] = true;
    }
  }

  function _addPools() private {
    _addPool(WETH, ARB, 500);
    _addPool(WETH, USDC, 500);
    _addPool(WETH, USDT, 500);
    _addPool(WETH, WBTC, 500);
    _addPool(DAI, USDC, 500);
    _addPool(USDT, USDC, 100);
    _addPool(ARB, USDC, 3000);
    _addPool(ARB, USDT, 3000);
  }

  function _initFlashLoanTokens() private {
    flashLoanTokens[ARB] = WETH;
  }

  function _flashLoan(address lTokenBorrowed, address lTokenCollateral, address borrower, uint256 amount) private {
    address underlying = ILToken(lTokenBorrowed).underlying();
    address asset = underlying == ETH ? WETH : underlying;

    address[] memory assets = new address[](1);
    uint256[] memory amounts = new uint256[](1);
    uint256[] memory modes = new uint256[](1);
    bytes memory params = abi.encode(FlashLoanParam(lTokenBorrowed, underlying, lTokenCollateral, borrower, amount));

    assets[0] = _getFlashLoanToken(asset);
    amounts[0] = assets[0] == WETH && asset != WETH ? _getETHAmountOfEqualValue(lTokenBorrowed, amount) : amount;
    modes[0] = 0;

    lendPool.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
  }

  function executeOperation(
    address[] calldata assets,
    uint256[] calldata amounts,
    uint256[] calldata premiums,
    address initiator,
    bytes calldata params
  ) external override returns (bool) {
    require(msg.sender == address(lendPool), "Liquidation: Invalid sender");
    require(initiator == address(this), "Liquidation Invalid initiator");
    require(assets.length == 1, "Liquidation: Invalid assets");
    require(amounts.length == 1, "Liquidation: Invalid amounts");
    require(premiums.length == 1, "Liquidation: Invalid premiums");
    FlashLoanParam memory param = abi.decode(params, (FlashLoanParam));

    if (param.underlyingBorrowed != assets[0] && param.underlyingBorrowed != ETH) {
      _swapForLiquidate(assets[0], param.underlyingBorrowed, 0);
    } else if (assets[0] == WETH && param.underlyingBorrowed == ETH) {
      IWETH(WETH).withdraw(amounts[0]);
    }

    _liquidate(param.lTokenBorrowed, param.lTokenCollateral, param.borrower, param.amount);

    if (ILToken(param.lTokenCollateral).underlying() == ETH) {
      IWETH(WETH).deposit{value: address(this).balance}();
    }

    _swapForRepay(param.lTokenCollateral, assets[0], amounts[0].add(premiums[0]));

    return true;
  }

  function _liquidate(address lTokenBorrowed, address lTokenCollateral, address borrower, uint256 amount) private {
    if (ILToken(lTokenBorrowed).underlying() == ETH) {
      core.liquidateBorrow{value: amount}(lTokenBorrowed, lTokenCollateral, borrower, 0);
    } else {
      core.liquidateBorrow(lTokenBorrowed, lTokenCollateral, borrower, amount);
    }

    uint256 lTokenCollateralBalance = ILToken(lTokenCollateral).balanceOf(address(this));
    _redeemToken(lTokenCollateral, lTokenCollateralBalance);
  }

  function _getFlashLoanToken(address token) private view returns (address) {
    if (flashLoanTokens[token] == address(0)) {
      return token;
    }

    return flashLoanTokens[token];
  }

  function _getTargetMarkets(address account) private view returns (address lTokenBorrowed, address lTokenCollateral) {
    uint256 maxSupplied;
    uint256 maxBorrowed;
    address[] memory markets = core.marketListOf(account);
    uint256[] memory prices = priceCalculator.getUnderlyingPrices(markets);

    for (uint256 i = 0; i < markets.length; i++) {
      uint256 borrowAmount = ILToken(markets[i]).borrowBalanceOf(account);
      uint256 supplyAmount = ILToken(markets[i]).underlyingBalanceOf(account);

      uint256 borrowValue = prices[i].mul(borrowAmount).div(10 ** _getDecimals(markets[i]));
      uint256 supplyValue = prices[i].mul(supplyAmount).div(10 ** _getDecimals(markets[i]));

      if (borrowValue > 0 && borrowValue > maxBorrowed) {
        maxBorrowed = borrowValue;
        lTokenBorrowed = markets[i];
      }

      uint256 collateralFactor = core.marketInfoOf(markets[i]).collateralFactor;
      if (collateralFactor > 0 && supplyValue > 0 && supplyValue > maxSupplied) {
        maxSupplied = supplyValue;
        lTokenCollateral = markets[i];
      }
    }
  }

  function _getMaxLiquidateAmount(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower
  ) private view returns (uint256 liquidateAmount) {
    uint256 borrowPrice = priceCalculator.getUnderlyingPrice(lTokenBorrowed);
    uint256 supplyPrice = priceCalculator.getUnderlyingPrice(lTokenCollateral);
    require(supplyPrice != 0 && borrowPrice != 0, "Liquidation: price error");

    uint256 borrowAmount = ILToken(lTokenBorrowed).borrowBalanceOf(borrower);
    uint256 supplyAmount = ILToken(lTokenCollateral).underlyingBalanceOf(borrower);

    uint256 borrowValue = borrowPrice.mul(borrowAmount).div(10 ** _getDecimals(lTokenBorrowed));
    uint256 supplyValue = supplyPrice.mul(supplyAmount).div(10 ** _getDecimals(lTokenCollateral));

    uint256 liquidationIncentive = core.liquidationIncentive();
    uint256 maxCloseValue = borrowValue.mul(core.closeFactor()).div(1e18);
    uint256 maxCloseValueWithIncentive = maxCloseValue.mul(liquidationIncentive).div(1e18);

    liquidateAmount = maxCloseValueWithIncentive < supplyValue
      ? maxCloseValue.mul(1e18).div(borrowPrice).div(10 ** (18 - _getDecimals(lTokenBorrowed)))
      : supplyValue.mul(1e36).div(liquidationIncentive).div(borrowPrice).div(10 ** (18 - _getDecimals(lTokenBorrowed)));
  }

  function _redeemToken(address lToken, uint256 lAmount) private {
    core.redeemToken(lToken, lAmount);
  }

  function _sendTokenToRebateDistributor(address token) private {
    address rebateDistributor = core.rebateDistributor();
    uint256 balance = token == ETH ? address(this).balance : IERC20(token).balanceOf(address(this));

    if (balance > 0 && token == ETH) {
      SafeToken.safeTransferETH(rebateDistributor, balance);
    } else if (balance > 0) {
      token.safeTransfer(rebateDistributor, balance);
    }
  }

  function _swapForLiquidate(address fromToken, address toToken, uint256 minReceiveAmount) private returns (uint256) {
    uint256 fromTokenBalance = IERC20(fromToken).balanceOf(address(this));
    if (toToken == ETH) {
      toToken = WETH;
    }
    return _swapToken(fromToken, fromTokenBalance, toToken, minReceiveAmount);
  }

  function _swapForRepay(address lTokenCollateral, address underlyingBorrowed, uint256 minReceiveAmount) private {
    address collateralToken = ILToken(lTokenCollateral).underlying();
    if (collateralToken == ETH) {
      collateralToken = WETH;
    }
    if (underlyingBorrowed == ETH) {
      underlyingBorrowed = WETH;
    }

    uint256 collateralTokenAmount = IERC20(collateralToken).balanceOf(address(this));
    require(collateralTokenAmount > 0, "Liquidation: Insufficent collateral for repay swap");

    _swapToken(collateralToken, collateralTokenAmount, underlyingBorrowed, minReceiveAmount);
  }

  function _swapToken(
    address tokenIn,
    uint256 amountIn,
    address tokenOut,
    uint256 amountOutMinimum
  ) private returns (uint256 amountOut) {
    if (tokenIn != tokenOut) {
      ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
        path: _getSwapPath(tokenIn, tokenOut),
        recipient: address(this),
        deadline: block.timestamp,
        amountIn: amountIn,
        amountOutMinimum: amountOutMinimum
      });

      amountOut = router.exactInput(params);
    }
  }

  function _getSwapPath(address tokenIn, address tokenOut) private view returns (bytes memory) {
    if (tokenIn == ETH) {
      tokenIn = WETH;
    }
    if (tokenOut == ETH) {
      tokenOut = WETH;
    }

    if (pools[tokenIn][tokenOut] > 0) {
      return abi.encodePacked(tokenIn, pools[tokenIn][tokenOut], tokenOut);
    }

    if (tokenIn != WETH && tokenOut != WETH && pools[tokenIn][WETH] > 0 && pools[WETH][tokenOut] > 0) {
      return abi.encodePacked(tokenIn, pools[tokenIn][WETH], WETH, pools[WETH][tokenOut], tokenOut);
    }

    if (tokenIn != USDC && tokenOut != USDC && pools[tokenIn][USDC] > 0 && pools[USDC][tokenOut] > 0) {
      return abi.encodePacked(tokenIn, pools[tokenIn][USDC], USDC, pools[USDC][tokenOut], tokenOut);
    }

    if (tokenIn != USDT && tokenOut != USDT && pools[tokenIn][USDT] > 0 && pools[USDT][tokenOut] > 0) {
      return abi.encodePacked(tokenIn, pools[tokenIn][USDT], USDT, pools[USDT][tokenOut], tokenOut);
    }

    if (tokenIn != ARB && tokenOut != ARB && pools[tokenIn][ARB] > 0 && pools[ARB][tokenOut] > 0) {
      return abi.encodePacked(tokenIn, pools[tokenIn][ARB], ARB, pools[ARB][tokenOut], tokenOut);
    }

    if (tokenIn == WBTC && tokenOut == DAI) {
      return abi.encodePacked(WBTC, pools[WBTC][WETH], WETH, pools[WETH][USDC], USDC, pools[USDC][DAI], DAI);
    }

    if (tokenIn == DAI && tokenIn == WBTC) {
      return abi.encodePacked(DAI, pools[DAI][USDC], USDC, pools[USDC][WETH], WETH, pools[WETH][WBTC]);
    }

    revert("Liquidation: path error");
  }

  function _addPool(address token0, address token1, uint24 fee) private {
    pools[token0][token1] = fee;
    pools[token1][token0] = fee;
  }

  function _getETHAmountOfEqualValue(address lToken, uint256 amount) private view returns (uint256 ethAmount) {
    uint256 ethPrice = priceCalculator.priceOfETH();
    uint256 tokenPrice = priceCalculator.getUnderlyingPrice(lToken);

    ethAmount = amount.mul((10 ** (18 - _getDecimals(lToken)))).mul(tokenPrice.mul(1e18).div(ethPrice)).div(1e18);
  }

  function _getDecimals(address lToken) private view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18;
    } else {
      decimals = IERC20(underlying).decimals();
    }
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/access/Ownable.sol";

contract Whitelist is Ownable {
  mapping(address => bool) private _whitelist;
  bool private _disable; // default - false means whitelist feature is working on. if true no more use of whitelist

  event Whitelisted(address indexed _address, bool whitelist);
  event EnableWhitelist();
  event DisableWhitelist();

  modifier onlyWhitelisted() {
    require(_disable || _whitelist[msg.sender], "Whitelist: caller is not on the whitelist");
    _;
  }

  function isWhitelist(address _address) public view returns (bool) {
    return _whitelist[_address];
  }

  function setWhitelist(address _address, bool _on) external onlyOwner {
    _whitelist[_address] = _on;

    emit Whitelisted(_address, _on);
  }

  function disableWhitelist(bool disable) external onlyOwner {
    _disable = disable;
    if (disable) {
      emit DisableWhitelist();
    } else {
      emit EnableWhitelist();
    }
  }

  uint256[49] private __gap;
}

File 35 of 68 : IFlashLoanReceiver.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

/**
 * @title IFlashLoanReceiver
 * @author Aave
 * @notice Defines the basic interface of a flashloan-receiver contract.
 * @dev Implement this interface to develop a flashloan-compatible flashLoanReceiver contract
 */
interface IFlashLoanReceiver {
  /**
   * @notice Executes an operation after receiving the flash-borrowed assets
   * @dev Ensure that the contract can return the debt + premium, e.g., has
   *      enough funds to repay and has approved the Pool to pull the total amount
   * @param assets The addresses of the flash-borrowed assets
   * @param amounts The amounts of the flash-borrowed assets
   * @param premiums The fee of each flash-borrowed asset
   * @param initiator The address of the flashloan initiator
   * @param params The byte-encoded params passed when initiating the flashloan
   * @return True if the execution of the operation succeeds, false otherwise
   */
  function executeOperation(
    address[] calldata assets,
    uint256[] calldata amounts,
    uint256[] calldata premiums,
    address initiator,
    bytes calldata params
  ) external returns (bool);
}

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.6.12;

/**
 * @title IPool
 * @author Aave
 * @notice Defines the basic interface for an Aave Pool.
 */
interface IPool {
  /**
   * @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
   * as long as the amount taken plus a fee is returned.
   * @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
   * into consideration. For further details please visit https://docs.aave.com/developers/
   * @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanReceiver interface
   * @param assets The addresses of the assets being flash-borrowed
   * @param amounts The amounts of the assets being flash-borrowed
   * @param interestRateModes Types of the debt to open if the flash loan is not returned:
   *   0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
   *   1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
   *   2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
   * @param onBehalfOf The address  that will receive the debt in the case of using on `modes` 1 or 2
   * @param params Variadic packed params to pass to the receiver as extra information
   * @param referralCode The code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function flashLoan(
    address receiverAddress,
    address[] calldata assets,
    uint256[] calldata amounts,
    uint256[] calldata interestRateModes,
    address onBehalfOf,
    bytes calldata params,
    uint16 referralCode
  ) external;

  /**
   * @notice Allows smartcontracts to access the liquidity of the pool within one transaction,
   * as long as the amount taken plus a fee is returned.
   * @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept
   * into consideration. For further details please visit https://docs.aave.com/developers/
   * @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanSimpleReceiver interface
   * @param asset The address of the asset being flash-borrowed
   * @param amount The amount of the asset being flash-borrowed
   * @param params Variadic packed params to pass to the receiver as extra information
   * @param referralCode The code used to register the integrator originating the operation, for potential rewards.
   *   0 if the action is executed directly by the user, without any middle-man
   */
  function flashLoanSimple(
    address receiverAddress,
    address asset,
    uint256 amount,
    bytes calldata params,
    uint16 referralCode
  ) external;
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "./IUniswapV3SwapCallback.sol";

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
  struct ExactInputSingleParams {
    address tokenIn;
    address tokenOut;
    uint24 fee;
    address recipient;
    uint256 deadline;
    uint256 amountIn;
    uint256 amountOutMinimum;
    uint160 sqrtPriceLimitX96;
  }

  /// @notice Swaps `amountIn` of one token for as much as possible of another token
  /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
  /// @return amountOut The amount of the received token
  function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

  struct ExactInputParams {
    bytes path;
    address recipient;
    uint256 deadline;
    uint256 amountIn;
    uint256 amountOutMinimum;
  }

  /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
  /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
  /// @return amountOut The amount of the received token
  function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

  struct ExactOutputSingleParams {
    address tokenIn;
    address tokenOut;
    uint24 fee;
    address recipient;
    uint256 deadline;
    uint256 amountOut;
    uint256 amountInMaximum;
    uint160 sqrtPriceLimitX96;
  }

  /// @notice Swaps as little as possible of one token for `amountOut` of another token
  /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
  /// @return amountIn The amount of the input token
  function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

  struct ExactOutputParams {
    bytes path;
    address recipient;
    uint256 deadline;
    uint256 amountOut;
    uint256 amountInMaximum;
  }

  /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
  /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
  /// @return amountIn The amount of the input token
  function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}

pragma solidity >=0.5.0;

interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint value) external returns (bool);
    function withdraw(uint) external;
}

pragma solidity >=0.5.0;

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

pragma solidity >=0.6.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
    }

    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }

    function safeTransferFrom(address token, address from, address to, uint value) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
    }

    function safeTransferETH(address to, uint value) internal {
        (bool success,) = to.call{value:value}(new bytes(0));
        require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
    }
}

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
  /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
  /// @dev In the implementation you must pay the pool tokens owed for the swap.
  /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
  /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
  /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
  /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
  /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
  /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
  function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol";
import "@uniswap/lib/contracts/libraries/TransferHelper.sol";

import "../library/SafeToken.sol";
import "../library/Whitelist.sol";

import "../interfaces/ICore.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/IPriceCalculator.sol";
import "../interfaces/IFlashLoanReceiver.sol";
import "../interfaces/IPool.sol";

contract Liquidation is IFlashLoanReceiver, Ownable, Whitelist, ReentrancyGuard {
  using SafeMath for uint256;
  using SafeToken for address;

  /* ========== CONSTANTS ============= */

  address private constant ETH = address(0);
  address private constant WETH = address(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
  address private constant WBTC = address(0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f);
  address private constant DAI = address(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);
  address private constant USDT = address(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
  address private constant USDC = address(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);

  IUniswapV2Factory private constant factory = IUniswapV2Factory(0xc35DADB65012eC5796536bD9864eD8773aBc74C4);
  IUniswapV2Router02 private constant router = IUniswapV2Router02(0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506);
  IPool private constant lendPool = IPool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);

  /* ========== STATE VARIABLES ========== */

  mapping(address => mapping(address => bool)) private tokenApproval;
  ICore public core;
  IPriceCalculator public priceCalculator;

  receive() external payable {}

  /* ========== Event ========== */

  event Liquidated(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower,
    uint256 amount,
    uint256 rebateAmount
  );

  /* ========== INITIALIZER ========== */

  constructor(address _core, address _priceCalculator) public {
    require(_core != address(0), "Liquidation: core address can't be zero");
    require(_priceCalculator != address(0), "Liquidation: priceCalculator address can't be zero");

    core = ICore(_core);
    priceCalculator = IPriceCalculator(_priceCalculator);

    _approveTokens();
  }

  function setPriceCalculator(address _priceCalculator) external onlyOwner {
    priceCalculator = IPriceCalculator(_priceCalculator);
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function liquidate(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower,
    uint256 amount
  ) external onlyWhitelisted nonReentrant {
    (uint256 collateralInUSD, , uint256 borrowInUSD) = core.accountLiquidityOf(borrower);
    require(borrowInUSD > collateralInUSD, "Liquidation: Insufficient shortfall");

    _flashLoan(lTokenBorrowed, lTokenCollateral, borrower, amount);

    address underlying = ILToken(lTokenBorrowed).underlying();

    emit Liquidated(
      lTokenBorrowed,
      lTokenCollateral,
      borrower,
      amount,
      underlying == ETH ? address(this).balance : IERC20(ILToken(lTokenBorrowed).underlying()).balanceOf(address(this))
    );

    _sendTokenToRebateDistributor(underlying);
  }

  /// @notice Liquidate borrower's max value debt using max value collateral
  /// @param borrower borrower account address
  function autoLiquidate(address borrower) external onlyWhitelisted nonReentrant {
    (uint256 collateralInUSD, , uint256 borrowInUSD) = core.accountLiquidityOf(borrower);
    require(borrowInUSD > collateralInUSD, "Liquidation: Insufficient shortfall");

    (address lTokenBorrowed, address lTokenCollateral) = _getTargetMarkets(borrower);
    uint256 liquidateAmount = _getMaxLiquidateAmount(lTokenBorrowed, lTokenCollateral, borrower);
    require(liquidateAmount > 0, "Liquidation: liquidate amount error");

    _flashLoan(lTokenBorrowed, lTokenCollateral, borrower, liquidateAmount);

    address underlying = ILToken(lTokenBorrowed).underlying();

    emit Liquidated(
      lTokenBorrowed,
      lTokenCollateral,
      borrower,
      liquidateAmount,
      underlying == ETH ? address(this).balance : IERC20(ILToken(lTokenBorrowed).underlying()).balanceOf(address(this))
    );

    _sendTokenToRebateDistributor(underlying);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _approveTokens() private {
    address[] memory markets = core.allMarkets();

    for (uint256 i = 0; i < markets.length; i++) {
      address token = ILToken(markets[i]).underlying();
      _approveToken(token, address(markets[i]));
      _approveToken(token, address(router));
      _approveToken(token, address(lendPool));
    }
    _approveToken(WETH, address(router));
    _approveToken(WETH, address(lendPool));
  }

  function _approveToken(address token, address spender) private {
    if (token != ETH && !tokenApproval[token][spender]) {
      token.safeApprove(spender, uint256(-1));
      tokenApproval[token][spender] = true;
    }
  }

  function _flashLoan(address lTokenBorrowed, address lTokenCollateral, address borrower, uint256 amount) private {
    address[] memory assets = new address[](1);
    uint256[] memory amounts = new uint256[](1);
    uint256[] memory modes = new uint256[](1);
    bytes memory params = abi.encode(lTokenBorrowed, lTokenCollateral, borrower, amount);

    address underlying = ILToken(lTokenBorrowed).underlying();

    assets[0] = underlying == ETH ? WETH : underlying;
    amounts[0] = amount;
    modes[0] = 0;

    lendPool.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
  }

  function executeOperation(
    address[] calldata assets,
    uint256[] calldata amounts,
    uint256[] calldata premiums,
    address initiator,
    bytes calldata params
  ) external override returns (bool) {
    require(msg.sender == address(lendPool), "Liquidation: Invalid sender");
    require(initiator == address(this), "Liquidation Invalid initiator");
    require(assets.length == 1, "Liquidation: Invalid assets");
    require(amounts.length == 1, "Liquidation: Invalid amounts");
    require(premiums.length == 1, "Liquidation: Invalid premiums");
    (address lTokenBorrowed, address lTokenCollateral, address borrower, uint256 liquidateAmount) = abi.decode(
      params,
      (address, address, address, uint256)
    );
    uint256 repayAmount = amounts[0].add(premiums[0]);

    if (assets[0] == WETH) {
      IWETH(WETH).withdraw(amounts[0]);
    }

    _liquidate(lTokenBorrowed, lTokenCollateral, borrower, liquidateAmount);

    if (ILToken(lTokenCollateral).underlying() == ETH) {
      IWETH(WETH).deposit{value: address(this).balance}();
    }

    if (lTokenCollateral != lTokenBorrowed) {
      _swapForRepay(lTokenCollateral, lTokenBorrowed, repayAmount);
    }

    return true;
  }

  function _liquidate(address lTokenBorrowed, address lTokenCollateral, address borrower, uint256 amount) private {
    if (ILToken(lTokenBorrowed).underlying() == ETH) {
      core.liquidateBorrow{value: amount}(lTokenBorrowed, lTokenCollateral, borrower, 0);
    } else {
      core.liquidateBorrow(lTokenBorrowed, lTokenCollateral, borrower, amount);
    }

    uint256 lTokenCollateralBalance = ILToken(lTokenCollateral).balanceOf(address(this));
    _redeemToken(lTokenCollateral, lTokenCollateralBalance);
  }

  function _getTargetMarkets(address account) private view returns (address lTokenBorrowed, address lTokenCollateral) {
    uint256 maxSupplied;
    uint256 maxBorrowed;
    address[] memory markets = core.marketListOf(account);
    uint256[] memory prices = priceCalculator.getUnderlyingPrices(markets);

    for (uint256 i = 0; i < markets.length; i++) {
      uint256 borrowAmount = ILToken(markets[i]).borrowBalanceOf(account);
      uint256 supplyAmount = ILToken(markets[i]).underlyingBalanceOf(account);

      uint256 borrowValue = prices[i].mul(borrowAmount).div(10 ** _getDecimals(markets[i]));
      uint256 supplyValue = prices[i].mul(supplyAmount).div(10 ** _getDecimals(markets[i]));

      if (borrowValue > 0 && borrowValue > maxBorrowed) {
        maxBorrowed = borrowValue;
        lTokenBorrowed = markets[i];
      }

      uint256 collateralFactor = core.marketInfoOf(markets[i]).collateralFactor;
      if (collateralFactor > 0 && supplyValue > 0 && supplyValue > maxSupplied) {
        maxSupplied = supplyValue;
        lTokenCollateral = markets[i];
      }
    }
  }

  function _getMaxLiquidateAmount(
    address lTokenBorrowed,
    address lTokenCollateral,
    address borrower
  ) private view returns (uint256 liquidateAmount) {
    uint256 borrowPrice = priceCalculator.getUnderlyingPrice(lTokenBorrowed);
    uint256 supplyPrice = priceCalculator.getUnderlyingPrice(lTokenCollateral);
    require(supplyPrice != 0 && borrowPrice != 0, "Liquidation: price error");

    uint256 borrowAmount = ILToken(lTokenBorrowed).borrowBalanceOf(borrower);
    uint256 supplyAmount = ILToken(lTokenCollateral).underlyingBalanceOf(borrower);

    uint256 borrowValue = borrowPrice.mul(borrowAmount).div(10 ** _getDecimals(lTokenBorrowed));
    uint256 supplyValue = supplyPrice.mul(supplyAmount).div(10 ** _getDecimals(lTokenCollateral));

    uint256 liquidationIncentive = core.liquidationIncentive();
    uint256 maxCloseValue = borrowValue.mul(core.closeFactor()).div(1e18);
    uint256 maxCloseValueWithIncentive = maxCloseValue.mul(liquidationIncentive).div(1e18);

    liquidateAmount = maxCloseValueWithIncentive < supplyValue
      ? maxCloseValue.mul(1e18).div(borrowPrice).div(10 ** (18 - _getDecimals(lTokenBorrowed)))
      : supplyValue.mul(1e36).div(liquidationIncentive).div(borrowPrice).div(10 ** (18 - _getDecimals(lTokenBorrowed)));
  }

  function _redeemToken(address lToken, uint256 lAmount) private {
    core.redeemToken(lToken, lAmount);
  }

  function _sendTokenToRebateDistributor(address token) private {
    address rebateDistributor = core.rebateDistributor();
    uint256 balance = token == ETH ? address(this).balance : IERC20(token).balanceOf(address(this));

    if (balance > 0 && token == ETH) {
      SafeToken.safeTransferETH(rebateDistributor, balance);
    } else if (balance > 0) {
      token.safeTransfer(rebateDistributor, balance);
    }
  }

  function _swapForRepay(address lTokenCollateral, address lTokenBorrowed, uint256 minReceiveAmount) private {
    address collateralToken = ILToken(lTokenCollateral).underlying();
    if (collateralToken == ETH) {
      collateralToken = WETH;
    }

    uint256 collateralTokenAmount = IERC20(collateralToken).balanceOf(address(this));
    require(collateralTokenAmount > 0, "Liquidation: Insufficent collateral");

    address borrowToken = ILToken(lTokenBorrowed).underlying();
    _swapToken(collateralToken, collateralTokenAmount, borrowToken, minReceiveAmount);
  }

  function _swapToken(address token, uint256 amount, address receiveToken, uint256 minReceiveAmount) private {
    address[] memory path = _getSwapPath(token == ETH ? WETH : token, receiveToken == ETH ? WETH : receiveToken);
    router.swapExactTokensForTokens(amount, minReceiveAmount, path, address(this), block.timestamp);
  }

  function _getSwapPath(address token1, address token2) private pure returns (address[] memory) {
    if (token1 == WETH || token2 == WETH) {
      address[] memory path = new address[](2);
      path[0] = token1;
      path[1] = token2;
      return path;
    } else {
      address[] memory path = new address[](3);
      path[0] = token1;
      path[1] = WETH;
      path[2] = token2;
      return path;
    }
  }

  function _getDecimals(address lToken) private view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18;
    } else {
      decimals = IERC20(underlying).decimals();
    }
  }
}

pragma solidity >=0.6.2;

import './IUniswapV2Router01.sol';

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

pragma solidity >=0.5.0;

interface IUniswapV2Pair {
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);

    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);
    function PERMIT_TYPEHASH() external pure returns (bytes32);
    function nonces(address owner) external view returns (uint);

    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint);
    function factory() external view returns (address);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function price0CumulativeLast() external view returns (uint);
    function price1CumulativeLast() external view returns (uint);
    function kLast() external view returns (uint);

    function mint(address to) external returns (uint liquidity);
    function burn(address to) external returns (uint amount0, uint amount1);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function skim(address to) external;
    function sync() external;

    function initialize(address, address) external;
}

pragma solidity >=0.5.0;

interface IUniswapV2Factory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);

    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function allPairs(uint) external view returns (address pair);
    function allPairsLength() external view returns (uint);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function setFeeTo(address) external;
    function setFeeToSetter(address) external;
}

pragma solidity >=0.6.2;

interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import "../library/Constant.sol";
import "../interfaces/IBEP20.sol";
import "../interfaces/IValidator.sol";
import "../interfaces/IRateModel.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/ICore.sol";
import "../interfaces/IMarketView.sol";

contract MarketView is IMarketView, Ownable, ReentrancyGuard {
  using SafeMath for uint256;

  /* ========== STATE VARIABLES ========== */

  mapping(address => IRateModel) public rateModel;

  /* ========== INITIALIZER ========== */

  constructor() public {}

  /* ========== RESTRICTED FUNCTIONS ========== */

  function setRateModel(address lToken, address _rateModel) public onlyOwner {
    require(_rateModel != address(0), "MarketView: invalid rate model address");
    rateModel[lToken] = IRateModel(_rateModel);
  }

  /* ========== VIEWS ========== */

  function borrowRatePerSec(address lToken) public view override returns (uint256) {
    Constant.AccrueSnapshot memory snapshot = pendingAccrueSnapshot(ILToken(lToken));
    return rateModel[lToken].getBorrowRate(ILToken(lToken).getCash(), snapshot.totalBorrow, snapshot.totalReserve);
  }

  function supplyRatePerSec(address lToken) public view override returns (uint256) {
    Constant.AccrueSnapshot memory snapshot = pendingAccrueSnapshot(ILToken(lToken));
    return
      rateModel[lToken].getSupplyRate(
        ILToken(lToken).getCash(),
        snapshot.totalBorrow,
        snapshot.totalReserve,
        ILToken(lToken).reserveFactor()
      );
  }

  function supplyAPR(address lToken) external view override returns (uint256) {
    return supplyRatePerSec(lToken).mul(365 days);
  }

  function borrowAPR(address lToken) external view override returns (uint256) {
    return borrowRatePerSec(lToken).mul(365 days);
  }

  /* ========== INTERNAL FUNCTIONS ========== */

  function pendingAccrueSnapshot(ILToken lToken) internal view returns (Constant.AccrueSnapshot memory) {
    Constant.AccrueSnapshot memory snapshot;
    snapshot.totalBorrow = lToken._totalBorrow();
    snapshot.totalReserve = lToken.totalReserve();
    snapshot.accInterestIndex = lToken.accInterestIndex();

    uint256 reserveFactor = lToken.reserveFactor();
    uint256 lastAccruedTime = lToken.lastAccruedTime();

    if (block.timestamp > lastAccruedTime && snapshot.totalBorrow > 0) {
      uint256 borrowRate = rateModel[address(lToken)].getBorrowRate(
        lToken.getCash(),
        snapshot.totalBorrow,
        snapshot.totalReserve
      );
      uint256 interestFactor = borrowRate.mul(block.timestamp.sub(lastAccruedTime));
      uint256 pendingInterest = snapshot.totalBorrow.mul(interestFactor).div(1e18);

      snapshot.totalBorrow = snapshot.totalBorrow.add(pendingInterest);
      snapshot.totalReserve = snapshot.totalReserve.add(pendingInterest.mul(reserveFactor).div(1e18));
      snapshot.accInterestIndex = snapshot.accInterestIndex.add(
        interestFactor.mul(snapshot.accInterestIndex).div(1e18)
      );
    }
    return snapshot;
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

interface IRateModel {
  function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view returns (uint256);

  function getSupplyRate(
    uint256 cash,
    uint256 borrows,
    uint256 reserves,
    uint256 reserveFactor
  ) external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../library/Constant.sol";

interface IMarketView {
  function borrowRatePerSec(address lToken) external view returns (uint256);

  function supplyRatePerSec(address lToken) external view returns (uint256);

  function supplyAPR(address lToken) external view returns (uint256);

  function borrowAPR(address lToken) external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import "../library/Constant.sol";

import "../interfaces/IBEP20.sol";
import "../interfaces/IValidator.sol";
import "../interfaces/IRateModel.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/ICore.sol";
import "../interfaces/IRebateDistributor.sol";

abstract contract Market is ILToken, Ownable, ReentrancyGuard {
  using SafeMath for uint256;

  /* ========== CONSTANT VARIABLES ========== */

  uint256 internal constant RESERVE_FACTOR_MAX = 1e18;
  uint256 internal constant DUST = 1000;

  address internal constant ETH = 0x0000000000000000000000000000000000000000;

  /* ========== STATE VARIABLES ========== */

  ICore public core;
  IRateModel public rateModel;
  IRebateDistributor public rebateDistributor;
  address public override underlying;

  uint256 public override totalSupply; // Total supply of lToken
  uint256 public override totalReserve;
  uint256 public override _totalBorrow;

  mapping(address => uint256) internal accountBalances;
  mapping(address => Constant.BorrowInfo) internal accountBorrows;

  uint256 public override reserveFactor;
  uint256 public override lastAccruedTime;
  uint256 public override accInterestIndex;

  /* ========== INITIALIZER ========== */

  receive() external payable {}

  /// @dev Initialization
  function __GMarket_init() internal {
    lastAccruedTime = block.timestamp;
    accInterestIndex = 1e18;
  }

  /* ========== MODIFIERS ========== */

  /// @dev 아직 처리되지 않은 totalBorrow, totalReserve, accInterestIndex 계산 및 저장
  modifier accrue() {
    if (block.timestamp > lastAccruedTime && address(rateModel) != address(0)) {
      uint256 borrowRate = rateModel.getBorrowRate(getCashPrior(), _totalBorrow, totalReserve);
      uint256 interestFactor = borrowRate.mul(block.timestamp.sub(lastAccruedTime));
      uint256 pendingInterest = _totalBorrow.mul(interestFactor).div(1e18);

      _totalBorrow = _totalBorrow.add(pendingInterest);
      totalReserve = totalReserve.add(pendingInterest.mul(reserveFactor).div(1e18));
      accInterestIndex = accInterestIndex.add(interestFactor.mul(accInterestIndex).div(1e18));
      lastAccruedTime = block.timestamp;
    }
    _;
  }

  /// @dev msg.sender 가 core address 인지 검증
  modifier onlyCore() {
    require(msg.sender == address(core), "LToken: only Core Contract");
    _;
  }

  modifier onlyRebateDistributor() {
    require(msg.sender == address(rebateDistributor), "LToken: only RebateDistributor");
    _;
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  /// @notice core address 를 설정
  /// @dev ZERO ADDRESS 로 설정할 수 없음
  ///      설정 이후에는 다른 주소로 변경할 수 없음
  /// @param _core core contract address
  function setCore(address _core) public onlyOwner {
    require(_core != address(0), "GMarket: invalid core address");
    require(address(core) == address(0), "GMarket: core already set");
    core = ICore(_core);
  }

  /// @notice underlying asset 의 token 설정
  /// @dev ZERO ADDRESS 로 설정할 수 없음
  ///      설정 이후에는 다른 주소로 변경할 수 없음
  /// @param _underlying Underlying token contract address
  function setUnderlying(address _underlying) public onlyOwner {
    require(_underlying != address(0), "GMarket: invalid underlying address");
    require(underlying == address(0), "GMarket: set underlying already");
    underlying = _underlying;
  }

  /// @notice rateModel 설정
  /// @param _rateModel 새로운 RateModel contract address
  function setRateModel(address _rateModel) public accrue onlyOwner {
    require(_rateModel != address(0), "GMarket: invalid rate model address");
    rateModel = IRateModel(_rateModel);
  }

  /// @notice reserve factor 변경
  /// @dev RESERVE_FACTOR_MAX 를 초과할 수 없음
  /// @param _reserveFactor 새로운 reserveFactor 값
  function setReserveFactor(uint256 _reserveFactor) public accrue onlyOwner {
    require(_reserveFactor <= RESERVE_FACTOR_MAX, "GMarket: invalid reserve factor");
    reserveFactor = _reserveFactor;
  }

  function setRebateDistributor(address _rebateDistributor) public onlyOwner {
    require(_rebateDistributor != address(0), "GMarket: invalid rebate distributor address");
    rebateDistributor = IRebateDistributor(_rebateDistributor);
  }

  /* ========== VIEWS ========== */

  function balanceOf(address account) external view override returns (uint256) {
    return accountBalances[account];
  }

  /// @notice account 의 AccountSnapshot 조회
  /// @param account account address
  function accountSnapshot(address account) external view override returns (Constant.AccountSnapshot memory) {
    Constant.AccountSnapshot memory snapshot;
    snapshot.lTokenBalance = accountBalances[account];
    snapshot.borrowBalance = borrowBalanceOf(account);
    snapshot.exchangeRate = exchangeRate();
    return snapshot;
  }

  /// @notice account 의 supply 된 underlying token 의 amount 조회
  /// @dev 원금에 붙은 이자를 포함
  /// @param account account address
  function underlyingBalanceOf(address account) external view override returns (uint256) {
    return accountBalances[account].mul(exchangeRate()).div(1e18);
  }

  /// @notice 계정의 borrow amount 조회
  /// @dev 원금에 붙은 이자를 포함
  function borrowBalanceOf(address account) public view override returns (uint256) {
    Constant.AccrueSnapshot memory snapshot = pendingAccrueSnapshot();
    Constant.BorrowInfo storage info = accountBorrows[account];

    if (info.borrow == 0) return 0;
    return info.borrow.mul(snapshot.accInterestIndex).div(info.interestIndex);
  }

  function totalBorrow() public view override returns (uint256) {
    Constant.AccrueSnapshot memory snapshot = pendingAccrueSnapshot();
    return snapshot.totalBorrow;
  }

  function exchangeRate() public view override returns (uint256) {
    if (totalSupply == 0) return 1e18;
    Constant.AccrueSnapshot memory snapshot = pendingAccrueSnapshot();
    return getCashPrior().add(snapshot.totalBorrow).sub(snapshot.totalReserve).mul(1e18).div(totalSupply);
  }

  function getCash() public view override returns (uint256) {
    return getCashPrior();
  }

  function getRateModel() external view override returns (address) {
    return address(rateModel);
  }

  function getAccInterestIndex() public view override returns (uint256) {
    Constant.AccrueSnapshot memory snapshot = pendingAccrueSnapshot();
    return snapshot.accInterestIndex;
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function accruedAccountSnapshot(address account) external override accrue returns (Constant.AccountSnapshot memory) {
    Constant.AccountSnapshot memory snapshot;
    Constant.BorrowInfo storage info = accountBorrows[account];
    if (info.interestIndex != 0) {
      info.borrow = info.borrow.mul(accInterestIndex).div(info.interestIndex);
      info.interestIndex = accInterestIndex;
    }

    snapshot.lTokenBalance = accountBalances[account];
    snapshot.borrowBalance = info.borrow;
    snapshot.exchangeRate = exchangeRate();
    return snapshot;
  }

  /// @notice View borrow balance amount after accure mutation
  function accruedBorrowBalanceOf(address account) external override accrue returns (uint256) {
    Constant.BorrowInfo storage info = accountBorrows[account];
    if (info.interestIndex != 0) {
      info.borrow = info.borrow.mul(accInterestIndex).div(info.interestIndex);
      info.interestIndex = accInterestIndex;
    }
    return info.borrow;
  }

  /// @notice View total borrow amount after accrue mutation
  function accruedTotalBorrow() external override accrue returns (uint256) {
    return _totalBorrow;
  }

  /// @notice View underlying token exchange rate after accure mutation
  function accruedExchangeRate() external override accrue returns (uint256) {
    return exchangeRate();
  }

  /* ========== INTERNAL FUNCTIONS ========== */

  /// @notice borrow info 업데이트
  /// @dev account 의 accountBorrows 를 변경하고, totalSupply 를 변경함
  /// @param account borrow 하는 address account
  /// @param addAmount 추가되는 borrow amount
  /// @param subAmount 제거되는 borrow amount
  function updateBorrowInfo(address account, uint256 addAmount, uint256 subAmount) internal {
    Constant.BorrowInfo storage info = accountBorrows[account];
    if (info.interestIndex == 0) {
      info.interestIndex = accInterestIndex;
    }

    info.borrow = info.borrow.mul(accInterestIndex).div(info.interestIndex).add(addAmount).sub(subAmount);
    info.interestIndex = accInterestIndex;
    _totalBorrow = _totalBorrow.add(addAmount).sub(subAmount);

    info.borrow = (info.borrow < DUST) ? 0 : info.borrow;
    _totalBorrow = (_totalBorrow < DUST) ? 0 : _totalBorrow;
  }

  /// @notice supply info 업데이트
  /// @dev account 의 accountBalances 를 변경하고, totalSupply 를 변경함
  /// @param account supply 하는 address account
  /// @param addAmount 추가되는 supply amount
  /// @param subAmount 제거되는 supply amount
  function updateSupplyInfo(address account, uint256 addAmount, uint256 subAmount) internal {
    accountBalances[account] = accountBalances[account].add(addAmount).sub(subAmount);
    totalSupply = totalSupply.add(addAmount).sub(subAmount);

    totalSupply = (totalSupply < DUST) ? 0 : totalSupply;
  }

  /// @notice contract 가 가지고 있는 underlying token amount 조회
  /// @dev underlying token 이 ETH 인 경우 msg.value 값을 빼서 계산함
  function getCashPrior() internal view returns (uint256) {
    return
      underlying == address(ETH) ? address(this).balance.sub(msg.value) : IBEP20(underlying).balanceOf(address(this));
  }

  /// @notice totalBorrow, totlaReserver, accInterestIdx 조회
  /// @dev 아직 계산되지 않은 pending interest 더한 값으로 조회
  ///      상태가 변겅되거나 저장되지는 않는다
  function pendingAccrueSnapshot() internal view returns (Constant.AccrueSnapshot memory) {
    Constant.AccrueSnapshot memory snapshot;
    snapshot.totalBorrow = _totalBorrow;
    snapshot.totalReserve = totalReserve;
    snapshot.accInterestIndex = accInterestIndex;

    if (block.timestamp > lastAccruedTime && _totalBorrow > 0) {
      uint256 borrowRate = rateModel.getBorrowRate(getCashPrior(), _totalBorrow, totalReserve);
      uint256 interestFactor = borrowRate.mul(block.timestamp.sub(lastAccruedTime));
      uint256 pendingInterest = _totalBorrow.mul(interestFactor).div(1e18);

      snapshot.totalBorrow = _totalBorrow.add(pendingInterest);
      snapshot.totalReserve = totalReserve.add(pendingInterest.mul(reserveFactor).div(1e18));
      snapshot.accInterestIndex = accInterestIndex.add(interestFactor.mul(accInterestIndex).div(1e18));
    }
    return snapshot;
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../interfaces/ILToken.sol";
import "../interfaces/ICore.sol";
import "../interfaces/IxLAB.sol";
import "../interfaces/ILABDistributor.sol";
import "../interfaces/IBEP20.sol";
import "../interfaces/IRebateDistributor.sol";
import "../interfaces/IPriceCalculator.sol";
import "../interfaces/IMarketView.sol";

contract Dashboard is Ownable {
  using SafeMath for uint256;

  struct BoostedAprDetails {
    address market;
    uint256 supplyAPR;
    uint256 borrowAPR;
    uint256 supplyRatio;
    uint256 borrowRatio;
    uint256 maxAPR;
    uint256 maxRatio;
  }

  /* ========== STATE VARIABLES ========== */

  ICore public core;
  IxLAB public xLAB;
  ILABDistributor public labDistributor;
  IRebateDistributor public rebateDistributor;
  IPriceCalculator public priceCalculator;
  IMarketView public marketView;

  uint public constant BOOST_MAX = 300;
  uint public constant BOOST_PORTION = 150;

  /* ========== INITIALIZER ========== */

  constructor(
    address _core,
    address _xlab,
    address _labDistributor,
    address _rebateDistributor,
    address _priceCalculator,
    address _marketView
  ) public {
    require(_core != address(0), "Dashboard: invalid core address");
    require(_xlab != address(0), "Dashboard: invalid xlab address");
    require(_labDistributor != address(0), "Dashboard: invalid labDistributor address");
    require(_rebateDistributor != address(0), "Dashboard: invalid rebateDistributor address");
    require(_priceCalculator != address(0), "Dashboard: invalid priceCalculator address");
    require(_marketView != address(0), "Dashboard: invalid marketView address");

    core = ICore(_core);
    xLAB = IxLAB(_xlab);
    labDistributor = ILABDistributor(_labDistributor);
    rebateDistributor = IRebateDistributor(_rebateDistributor);
    priceCalculator = IPriceCalculator(_priceCalculator);
    marketView = IMarketView(_marketView);
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function userBalanceInfo(address account) public view returns (uint256 share, uint256 veBalance, uint256 total) {
    share = xLAB.shareOf(account);
    veBalance = IBEP20(address(xLAB)).balanceOf(account);
    total = IBEP20(address(xLAB)).totalSupply();
  }

  function userNextBalanceInfo(
    address account,
    uint256 additionalVeAmount
  ) public view returns (uint256 share, uint256 veBalance, uint total) {
    (, uint256 oldVeBalance, uint256 oldTotal) = userBalanceInfo(account);

    total = oldTotal.add(additionalVeAmount);
    veBalance = oldVeBalance.add(additionalVeAmount);
    share = veBalance.mul(1e18).div(total);
  }

  function getNextMaxBoostedApr(
    address account,
    uint256 additionalVeAmount
  ) external view returns (BoostedAprDetails memory) {
    (, uint256 nextVeAmount, uint256 nextToal) = userNextBalanceInfo(account, additionalVeAmount);
    return getMaxBoostedApr(account, nextVeAmount, nextToal);
  }

  function getMaxBoostedApr(
    address account,
    uint256 score,
    uint256 totalScore
  ) public view returns (BoostedAprDetails memory) {
    address[] memory markets = core.allMarkets();
    BoostedAprDetails memory maxAPRInfo;
    for (uint256 i = 0; i < markets.length; i++) {
      BoostedAprDetails memory aprDetailInfo = _calculateBoostedAprInfo(account, markets[i], score, totalScore);
      if (maxAPRInfo.maxAPR < aprDetailInfo.supplyAPR) {
        maxAPRInfo.maxAPR = aprDetailInfo.supplyAPR;
        maxAPRInfo.maxRatio = aprDetailInfo.supplyRatio;
      }
      if (maxAPRInfo.maxAPR < aprDetailInfo.borrowAPR) {
        maxAPRInfo.maxAPR = aprDetailInfo.borrowAPR;
        maxAPRInfo.maxRatio = aprDetailInfo.borrowRatio;
      }
    }
    return maxAPRInfo;
  }

  function usersMonthlyProfit(
    address account
  )
    external
    view
    returns (
      uint256 supplyBaseProfits,
      uint256 supplyRewardProfits,
      uint256 borrowBaseProfits,
      uint256 borrowRewardProfits
    )
  {
    address[] memory markets = core.allMarkets();
    uint[] memory prices = priceCalculator.getUnderlyingPrices(markets);
    supplyBaseProfits = 0;
    supplyRewardProfits = 0;
    borrowBaseProfits = 0;
    borrowRewardProfits = 0;

    for (uint256 i = 0; i < markets.length; i++) {
      Constant.DistributionAPY memory apyDistribution = labDistributor.apyDistributionOf(markets[i], account);
      uint256 decimals = _getDecimals(markets[i]);
      {
        uint256 supplyBalance = ILToken(markets[i]).underlyingBalanceOf(account);
        uint256 supplyAPY = marketView.supplyRatePerSec(markets[i]).mul(365 days);
        uint256 supplyInUSD = supplyBalance.mul(10 ** (18 - decimals)).mul(prices[i]).div(1e18);
        uint256 supplyMonthlyProfit = supplyInUSD.mul(supplyAPY).div(12).div(1e18);
        uint256 supplyGRVMonthlyProfit = supplyInUSD.mul(apyDistribution.apyAccountSupplyLab).div(12).div(1e18);

        supplyBaseProfits = supplyBaseProfits.add(supplyMonthlyProfit);
        supplyRewardProfits = supplyRewardProfits.add(supplyGRVMonthlyProfit);
      }
      {
        uint256 borrowBalance = ILToken(markets[i]).borrowBalanceOf(account);
        uint256 borrowAPY = marketView.borrowRatePerSec(markets[i]).mul(365 days);
        uint256 borrowInUSD = borrowBalance.mul(10 ** (18 - decimals)).mul(prices[i]).div(1e18);
        uint256 borrowMonthlyProfit = borrowInUSD.mul(borrowAPY).div(12).div(1e18);
        uint256 borrowGRVMonthlyProfit = borrowInUSD.mul(apyDistribution.apyAccountBorrowLab).div(12).div(1e18);

        borrowBaseProfits = borrowBaseProfits.add(borrowMonthlyProfit);
        borrowRewardProfits = borrowRewardProfits.add(borrowGRVMonthlyProfit);
      }
    }
  }

  function lockAPRInfo(
    uint256 amount
  )
    external
    view
    returns (
      uint256 lockAPR1Month,
      uint256 lockAPR3Month,
      uint256 lockAPR6Month,
      uint256 lockAPR9Month,
      uint256 lockAPR12Month,
      uint256 lockAPR24Month
    )
  {
    lockAPR1Month = rebateDistributor.indicativeAPROf(amount, 30 days);
    lockAPR3Month = rebateDistributor.indicativeAPROf(amount, 90 days);
    lockAPR6Month = rebateDistributor.indicativeAPROf(amount, 180 days);
    lockAPR9Month = rebateDistributor.indicativeAPROf(amount, 270 days);
    lockAPR12Month = rebateDistributor.indicativeAPROf(amount, 365 days);
    lockAPR24Month = rebateDistributor.indicativeAPROf(amount, 730 days);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _calculatePreBoostedSupply(
    address market,
    address user,
    uint256 userScore,
    uint256 totalScore
  ) private view returns (uint) {
    uint defaultSupply = ILToken(market).balanceOf(user);
    uint boostedSupply = defaultSupply;

    if (userScore > 0 && totalScore > 0) {
      uint scoreBoosted = ILToken(market).totalSupply().mul(userScore).div(totalScore).mul(BOOST_PORTION).div(100);
      boostedSupply = boostedSupply.add(scoreBoosted);
    }
    return Math.min(boostedSupply, defaultSupply.mul(BOOST_MAX).div(100));
  }

  function _calculatePreBoostedBorrow(
    address market,
    address user,
    uint256 userScore,
    uint256 totalScore
  ) private view returns (uint) {
    uint accInterestIndex = ILToken(market).getAccInterestIndex();
    uint defaultBorrow = ILToken(market).borrowBalanceOf(user).mul(1e18).div(accInterestIndex);
    uint boostedBorrow = defaultBorrow;

    if (userScore > 0 && totalScore > 0) {
      uint totalBorrow = ILToken(market).totalBorrow().mul(1e18).div(accInterestIndex);
      uint scoreBoosted = totalBorrow.mul(userScore).div(totalScore).mul(BOOST_PORTION).div(100);
      boostedBorrow = boostedBorrow.add(scoreBoosted);
    }
    return Math.min(boostedBorrow, defaultBorrow.mul(BOOST_MAX).div(100));
  }

  function _getBoostedInfo(
    address market,
    address user,
    uint256 userScore,
    uint256 totalScore
  ) private view returns (uint256 boostedSupply, uint256 boostedBorrow) {
    boostedSupply = _calculatePreBoostedSupply(market, user, userScore, totalScore);
    boostedBorrow = _calculatePreBoostedBorrow(market, user, userScore, totalScore);
  }

  function _calculateBoostedAprInfo(
    address account,
    address market,
    uint256 score,
    uint256 totalScore
  ) private view returns (BoostedAprDetails memory) {
    BoostedAprDetails memory aprDetailInfo;
    aprDetailInfo.market = market;
    Constant.DistributionAPY memory apyDistribution = labDistributor.apyDistributionOf(market, account);
    {
      uint256 accountSupply = ILToken(market).balanceOf(account);
      uint256 accountBorrow = ILToken(market).borrowBalanceOf(account).mul(1e18).div(
        ILToken(market).getAccInterestIndex()
      );

      (uint256 preBoostedSupply, uint256 preBoostedBorrow) = _getBoostedInfo(market, account, score, totalScore);
      uint256 expectedApyAccountSupplyGRV = accountSupply > 0
        ? apyDistribution.apySupplyLab.mul(preBoostedSupply).div(accountSupply)
        : 0;

      uint256 expectedApyAccountBorrowGRV = accountBorrow > 0
        ? apyDistribution.apyBorrowLab.mul(preBoostedBorrow).div(accountBorrow)
        : 0;

      uint256 boostedSupplyRatio = accountSupply > 0 ? preBoostedSupply.mul(1e18).div(accountSupply) : 0;
      uint256 boostedBorrowRatio = accountBorrow > 0 ? preBoostedBorrow.mul(1e18).div(accountBorrow) : 0;
      aprDetailInfo.supplyAPR = expectedApyAccountSupplyGRV;
      aprDetailInfo.borrowAPR = expectedApyAccountBorrowGRV;
      aprDetailInfo.supplyRatio = boostedSupplyRatio;
      aprDetailInfo.borrowRatio = boostedBorrowRatio;
    }
    return aprDetailInfo;
  }

  function _getDecimals(address lToken) private view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18;
    } else {
      decimals = IBEP20(underlying).decimals();
    }
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../interfaces/IMarketDashboard.sol";
import "../interfaces/ILABDistributor.sol";
import "../interfaces/IMarketView.sol";
import "../interfaces/ILToken.sol";
import "../interfaces/ICore.sol";
import "../interfaces/IBEP20.sol";
import "../interfaces/IPriceCalculator.sol";

contract MarketDashboard is IMarketDashboard, Ownable {
  using SafeMath for uint256;

  /* ========== STATE VARIABLES ========== */

  ILABDistributor public grvDistributor;
  IMarketView public marketView;
  ICore public core;
  IPriceCalculator public priceCalculator;

  /* ========== INITIALIZER ========== */

  constructor(address _core, address _grvDistributor, address _marketView, address _priceCalculator) public {
    require(_grvDistributor != address(0), "MarketDashboard: grvDistributor address can't be zero");
    require(_marketView != address(0), "MarketDashboard: MarketView address can't be zero");
    require(_core != address(0), "MarketDashboard: core address can't be zero");
    require(_priceCalculator != address(0), "MarketDashboard: priceCalculator address can't be zero");

    core = ICore(_core);
    grvDistributor = ILABDistributor(_grvDistributor);
    marketView = IMarketView(_marketView);
    priceCalculator = IPriceCalculator(_priceCalculator);
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function setDistributor(address _grvDistributor) external onlyOwner {
    require(_grvDistributor != address(0), "MarketDashboard: invalid grvDistributor address");
    grvDistributor = ILABDistributor(_grvDistributor);
  }

  function setMarketView(address _marketView) external onlyOwner {
    require(_marketView != address(0), "MarketDashboard: invalid MarketView address");
    marketView = IMarketView(_marketView);
  }

  function setPriceCalculator(address _priceCalculator) external onlyOwner {
    require(_priceCalculator != address(0), "MarketDashboard: invalid priceCalculator address");
    priceCalculator = IPriceCalculator(_priceCalculator);
  }

  /* ========== VIEWS ========== */

  function marketDataOf(address market) external view override returns (MarketData memory) {
    MarketData memory marketData;
    Constant.DistributionAPY memory apyDistribution = grvDistributor.apyDistributionOf(market, address(0));
    Constant.DistributionInfo memory distributionInfo = grvDistributor.distributionInfoOf(market);
    ILToken lToken = ILToken(market);

    marketData.lToken = market;

    marketData.apySupply = marketView.supplyRatePerSec(market).mul(365 days);
    marketData.apyBorrow = marketView.borrowRatePerSec(market).mul(365 days);
    marketData.apySupplyGRV = apyDistribution.apySupplyLab;
    marketData.apyBorrowGRV = apyDistribution.apyBorrowLab;

    marketData.totalSupply = lToken.totalSupply().mul(lToken.exchangeRate()).div(1e18);
    marketData.totalBorrows = lToken.totalBorrow();
    marketData.totalBoostedSupply = distributionInfo.totalBoostedSupply;
    marketData.totalBoostedBorrow = distributionInfo.totalBoostedBorrow;

    marketData.cash = lToken.getCash();
    marketData.reserve = lToken.totalReserve();
    marketData.reserveFactor = lToken.reserveFactor();
    marketData.collateralFactor = core.marketInfoOf(market).collateralFactor;
    marketData.exchangeRate = lToken.exchangeRate();
    marketData.borrowCap = core.marketInfoOf(market).borrowCap;
    marketData.accInterestIndex = lToken.getAccInterestIndex();
    return marketData;
  }

  function usersMonthlyProfit(
    address account
  )
    external
    view
    override
    returns (
      uint256 supplyBaseProfits,
      uint256 supplyRewardProfits,
      uint256 borrowBaseProfits,
      uint256 borrowRewardProfits
    )
  {
    address[] memory markets = core.allMarkets();
    uint[] memory prices = priceCalculator.getUnderlyingPrices(markets);
    supplyBaseProfits = 0;
    supplyRewardProfits = 0;
    borrowBaseProfits = 0;
    borrowRewardProfits = 0;

    for (uint256 i = 0; i < markets.length; i++) {
      Constant.DistributionAPY memory apyDistribution = grvDistributor.apyDistributionOf(markets[i], account);
      uint256 decimals = _getDecimals(markets[i]);
      {
        uint256 supplyBalance = ILToken(markets[i]).underlyingBalanceOf(account);
        uint256 supplyAPY = marketView.supplyRatePerSec(markets[i]).mul(365 days);
        uint256 supplyInUSD = supplyBalance.mul(10 ** (18 - decimals)).mul(prices[i]).div(1e18);
        uint256 supplyMonthlyProfit = supplyInUSD.mul(supplyAPY).div(12).div(1e18);
        uint256 supplyGRVMonthlyProfit = supplyInUSD.mul(apyDistribution.apyAccountSupplyLab).div(12).div(1e18);

        supplyBaseProfits = supplyBaseProfits.add(supplyMonthlyProfit);
        supplyRewardProfits = supplyRewardProfits.add(supplyGRVMonthlyProfit);
      }
      {
        uint256 borrowBalance = ILToken(markets[i]).borrowBalanceOf(account);
        uint256 borrowAPY = marketView.borrowRatePerSec(markets[i]).mul(365 days);
        uint256 borrowInUSD = borrowBalance.mul(10 ** (18 - decimals)).mul(prices[i]).div(1e18);
        uint256 borrowMonthlyProfit = borrowInUSD.mul(borrowAPY).div(12).div(1e18);
        uint256 borrowGRVMonthlyProfit = borrowInUSD.mul(apyDistribution.apyAccountBorrowLab).div(12).div(1e18);

        borrowBaseProfits = borrowBaseProfits.add(borrowMonthlyProfit);
        borrowRewardProfits = borrowRewardProfits.add(borrowGRVMonthlyProfit);
      }
    }
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _getDecimals(address lToken) internal view returns (uint256 decimals) {
    address underlying = ILToken(lToken).underlying();
    if (underlying == address(0)) {
      decimals = 18; // ETH
    } else {
      decimals = IBEP20(underlying).decimals();
    }
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface IMarketDashboard {
  struct MarketData {
    address lToken;
    uint256 apySupply;
    uint256 apyBorrow;
    uint256 apySupplyGRV;
    uint256 apyBorrowGRV;
    uint256 totalSupply;
    uint256 totalBorrows;
    uint256 totalBoostedSupply;
    uint256 totalBoostedBorrow;
    uint256 cash;
    uint256 reserve;
    uint256 reserveFactor;
    uint256 collateralFactor;
    uint256 exchangeRate;
    uint256 borrowCap;
    uint256 accInterestIndex;
  }

  function marketDataOf(address market) external view returns (MarketData memory);

  function usersMonthlyProfit(
    address account
  )
    external
    view
    returns (
      uint256 supplyBaseProfits,
      uint256 supplyRewardProfits,
      uint256 borrowBaseProfits,
      uint256 borrowRewardProfits
    );
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "./interfaces/ILeverager.sol";
import "./interfaces/IBEP20.sol";
import "./interfaces/ILToken.sol";
import "./interfaces/ICore.sol";

import "./library/SafeToken.sol";
import "./library/Constant.sol";

contract Leverager is ILeverager, Ownable, ReentrancyGuard, Pausable {
    using SafeMath for uint256;
    using SafeToken for address;

    uint256 public constant RATIO_DIVISOR = 10000;

    ICore public core;

    address public lETH;

    // initializer
    bool public initialized;

    receive() external payable {}

    /* ========== INITIALIZER ========== */

    constructor() public {}

    modifier onlyListedMarket(address lToken) {
        Constant.MarketInfo memory marketInfo = core.marketInfoOf(lToken);
        require(marketInfo.isListed, "Leverager: invalid market");
        _;
    }

    function initialize(
        ICore _core,
        address _lETH
    ) external onlyOwner {
        require(initialized == false, "already initialized");
        require(address(_core) != address(0), "Not a valid address");
        require(_lETH != address(0), "Not a valid address");

        core = _core;
        lETH = _lETH;

        initialized = true;
    }

    /**
     * @dev Loop the deposit and borrow of an asset
	 * @param lToken for loop
	 * @param amount for the initial deposit
	 * @param borrowRatio Ratio of tokens to borrow
	 * @param loopCount Repeat count for loop
	 * @param isBorrow true when the loop without deposit tokens
	 **/
    function loop(
        address lToken,
        uint256 amount,
        uint256 borrowRatio,
        uint256 loopCount,
        bool isBorrow
    ) external onlyListedMarket(lToken) {
        require(borrowRatio <= RATIO_DIVISOR, "Invalid ratio");
        address asset = ILToken(lToken).underlying();

        // true when the loop without deposit tokens
        if (!isBorrow) {
            asset.safeTransferFrom(msg.sender, address(this), amount);
        }
        if (IBEP20(asset).allowance(address(this), lToken) == 0) {
            asset.safeApprove(address(lToken), uint256(-1));
        }
        if (IBEP20(asset).allowance(address(this), address(core)) == 0) {
            asset.safeApprove(address(core), uint256(-1));
        }

        if (!isBorrow) {
            core.supplyBehalf(msg.sender, lToken, amount);
        }

        for (uint256 i = 0; i < loopCount; i++) {
            amount = amount.mul(borrowRatio).div(RATIO_DIVISOR);
            core.borrowBehalf(msg.sender, lToken, amount);
            core.supplyBehalf(msg.sender, lToken, amount);
        }
    }

    function loopETH(uint256 borrowRatio, uint256 loopCount) external payable {
        require(borrowRatio <= RATIO_DIVISOR, "Invalid ratio");
        uint256 amount = msg.value;

        core.supplyBehalf{value: amount}(msg.sender, lETH, 0);

        for (uint256 i = 0; i < loopCount; i++) {
            amount = amount.mul(borrowRatio).div(RATIO_DIVISOR);
            core.borrowBehalf(msg.sender, lETH, amount);
            core.supplyBehalf{value: amount}(msg.sender, lETH, 0);
        }
    }
}

File 55 of 68 : ILeverager.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface ILeverager {
    event FeePercentUpdated(uint256 _feePercent);
    event TreasuryUpdated(address indexed _treasury);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../../interfaces/IRateModel.sol";

/// @dev https://bscscan.com/address/0x9535c1f26df97451671913f7aeda646c0f1eda85#readProxyContract
contract RateModelSlope is IRateModel, Ownable {
  using SafeMath for uint256;

  uint256 private baseRatePerYear;
  uint256 private slopePerYearFirst;
  uint256 private slopePerYearSecond;
  uint256 private optimal;

  // initializer
  bool public initialized;

  constructor() public {}

  /// @notice Contract 초기화 변수 설정
  /// @param _baseRatePerYear 기본 이자율
  /// @param _slopePerYearFirst optimal 이전 이자 계수
  /// @param _slopePerYearSecond optimal 초과 이자 계수
  /// @param _optimal double-slope optimal
  function initialize(
    uint256 _baseRatePerYear,
    uint256 _slopePerYearFirst,
    uint256 _slopePerYearSecond,
    uint256 _optimal
  ) external onlyOwner {
    require(initialized == false, "already initialized");
    baseRatePerYear = _baseRatePerYear;
    slopePerYearFirst = _slopePerYearFirst;
    slopePerYearSecond = _slopePerYearSecond;
    optimal = _optimal;

    initialized = true;
  }

  function utilizationRate(uint256 cash, uint256 borrows, uint256 reserves) public pure returns (uint256) {
    if (reserves >= cash.add(borrows)) return 1e18;
    return Math.min(borrows.mul(1e18).div(cash.add(borrows).sub(reserves)), 1e18);
  }

  function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) public view override returns (uint256) {
    uint256 utilization = utilizationRate(cash, borrows, reserves);
    if (utilization < optimal) {
      return baseRatePerYear.add(utilization.mul(slopePerYearFirst).div(optimal)).div(365 days);
    } else {
      uint256 ratio = utilization.sub(optimal).mul(1e18).div(uint256(1e18).sub(optimal));
      return baseRatePerYear.add(slopePerYearFirst).add(ratio.mul(slopePerYearSecond).div(1e18)).div(365 days);
    }
  }

  function getSupplyRate(
    uint256 cash,
    uint256 borrows,
    uint256 reserves,
    uint256 reserveFactor
  ) public view override returns (uint256) {
    uint256 oneMinusReserveFactor = uint256(1e18).sub(reserveFactor);
    uint256 borrowRate = getBorrowRate(cash, borrows, reserves);
    uint256 rateToPool = borrowRate.mul(oneMinusReserveFactor).div(1e18);
    return utilizationRate(cash, borrows, reserves).mul(rateToPool).div(1e18);
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/Math.sol";

import "../library/SafeToken.sol";

import "./Market.sol";

import "../interfaces/IWETH.sol";

contract LToken is Market {
  using SafeMath for uint256;
  using SafeToken for address;

  /* ========== STATE VARIABLES ========== */

  string public name;
  string public symbol;
  uint8 public decimals;

  // initializer
  bool public initialized;

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

  /* ========== EVENT ========== */

  event Mint(address minter, uint256 mintAmount);
  event Redeem(address account, uint underlyingAmount, uint lTokenAmount);

  event Borrow(address account, uint256 ammount, uint256 accountBorrow);
  event RepayBorrow(address payer, address borrower, uint256 amount, uint256 accountBorrow);
  event LiquidateBorrow(
    address liquidator,
    address borrower,
    uint256 amount,
    address lTokenCollateral,
    uint256 seizeAmount
  );

  event Transfer(address indexed from, address indexed to, uint256 amount);
  event Approval(address indexed owner, address indexed spender, uint256 amount);

  /* ========== INITIALIZER ========== */

  constructor() public {}

  function initialize(string memory _name, string memory _symbol, uint8 _decimals) external onlyOwner {
    require(initialized == false, "already initialized");
    __GMarket_init();

    name = _name;
    symbol = _symbol;
    decimals = _decimals;
    initialized = true;
  }

  /* ========== VIEWS ========== */

  function allowance(address account, address spender) external view override returns (uint256) {
    return _transferAllowances[account][spender];
  }

  function getOwner() external view returns (address) {
    return owner();
  }

  /* ========== MUTATIVE FUNCTIONS ========== */

  function transfer(address dst, uint256 amount) external override accrue nonReentrant returns (bool) {
    core.transferTokens(msg.sender, msg.sender, dst, amount);
    return true;
  }

  function transferFrom(address src, address dst, uint256 amount) external override accrue nonReentrant returns (bool) {
    core.transferTokens(msg.sender, src, dst, amount);
    return true;
  }

  function approve(address spender, uint256 amount) external override returns (bool) {
    _transferAllowances[msg.sender][spender] = amount;
    emit Approval(msg.sender, spender, amount);
    return true;
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function supply(address account, uint256 uAmount) external payable override accrue onlyCore returns (uint256) {
    uint256 exchangeRate = exchangeRate();
    uAmount = underlying == address(ETH) ? msg.value : uAmount;
    uAmount = _doTransferIn(account, uAmount);
    uint256 lAmount = uAmount.mul(1e18).div(exchangeRate);
    require(lAmount > 0, "LToken: invalid lAmount");
    updateSupplyInfo(account, lAmount, 0);

    emit Mint(account, lAmount);
    emit Transfer(address(0), account, lAmount);
    return lAmount;
  }

  function supplyBehalf(
    address account,
    address supplier,
    uint256 uAmount
  ) external payable override accrue onlyCore returns (uint256) {
    uint256 exchangeRate = exchangeRate();
    uAmount = underlying == address(ETH) ? msg.value : uAmount;
    uAmount = _doTransferIn(account, uAmount);
    uint256 lAmount = uAmount.mul(1e18).div(exchangeRate);
    require(lAmount > 0, "LToken: invalid lAmount");
    updateSupplyInfo(supplier, lAmount, 0);

    emit Mint(supplier, lAmount);
    emit Transfer(address(0), supplier, lAmount);
    return lAmount;
  }

  function redeemToken(address redeemer, uint256 lAmount) external override accrue onlyCore returns (uint256) {
    return _redeem(redeemer, lAmount, 0);
  }

  function redeemUnderlying(address redeemer, uint256 uAmount) external override accrue onlyCore returns (uint256) {
    return _redeem(redeemer, 0, uAmount);
  }

  function borrow(address account, uint256 amount) external override accrue onlyCore returns (uint256) {
    require(getCash() >= amount, "LToken: borrow amount exceeds cash");
    updateBorrowInfo(account, amount, 0);
    _doTransferOut(account, amount);

    emit Borrow(account, amount, borrowBalanceOf(account));
    return amount;
  }

  function borrowBehalf(
    address account,
    address borrower,
    uint256 amount
  ) external override accrue onlyCore returns (uint256) {
    require(getCash() >= amount, "LToken: borrow amount exceeds cash");
    updateBorrowInfo(borrower, amount, 0);
    _doTransferOut(account, amount);

    emit Borrow(borrower, amount, borrowBalanceOf(borrower));
    return amount;
  }

  function repayBorrow(address account, uint256 amount) external payable override accrue onlyCore returns (uint256) {
    if (amount == uint256(-1)) {
      amount = borrowBalanceOf(account);
    }
    return _repay(account, account, underlying == address(ETH) ? msg.value : amount);
  }

  function liquidateBorrow(
    address lTokenCollateral,
    address liquidator,
    address borrower,
    uint256 amount
  )
    external
    payable
    override
    accrue
    onlyCore
    returns (uint256 seizeLAmount, uint256 rebateLAmount, uint256 liquidatorLAmount)
  {
    require(borrower != liquidator, "LToken: cannot liquidate yourself");
    amount = underlying == address(ETH) ? msg.value : amount;
    amount = _repay(liquidator, borrower, amount);
    require(amount > 0 && amount < uint256(-1), "LToken: invalid repay amount");

    (seizeLAmount, rebateLAmount, liquidatorLAmount) = IValidator(core.validator()).lTokenAmountToSeize(
      address(this),
      lTokenCollateral,
      amount
    );

    require(ILToken(payable(lTokenCollateral)).balanceOf(borrower) >= seizeLAmount, "LToken: too much seize amount");

    emit LiquidateBorrow(liquidator, borrower, amount, lTokenCollateral, seizeLAmount);
  }

  function seize(address liquidator, address borrower, uint256 lAmount) external override accrue onlyCore nonReentrant {
    accountBalances[borrower] = accountBalances[borrower].sub(lAmount);
    accountBalances[liquidator] = accountBalances[liquidator].add(lAmount);

    emit Transfer(borrower, liquidator, lAmount);
  }

  function withdrawReserves() external override accrue onlyRebateDistributor nonReentrant {
    if (getCash() >= totalReserve) {
      uint256 amount = totalReserve;

      if (amount > 0) {
        totalReserve = 0;
        _doTransferOut(address(rebateDistributor), amount);
      }
    }
  }

  function transferTokensInternal(
    address spender,
    address src,
    address dst,
    uint256 amount
  ) external override onlyCore {
    require(
      src != dst && IValidator(core.validator()).redeemAllowed(address(this), src, amount),
      "LToken: cannot transfer"
    );
    require(amount != 0, "LToken: zero amount");
    uint256 _allowance = spender == src ? uint256(-1) : _transferAllowances[src][spender];
    uint256 _allowanceNew = _allowance.sub(amount, "LToken: transfer amount exceeds allowance");

    accountBalances[src] = accountBalances[src].sub(amount);
    accountBalances[dst] = accountBalances[dst].add(amount);

    if (_allowance != uint256(-1)) {
      _transferAllowances[src][spender] = _allowanceNew;
    }
    emit Transfer(src, dst, amount);
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _doTransferIn(address from, uint256 amount) private returns (uint256) {
    if (underlying == address(ETH)) {
      require(msg.value >= amount, "LToken: value mismatch");
      return Math.min(msg.value, amount);
    } else {
      uint256 balanceBefore = IBEP20(underlying).balanceOf(address(this));
      underlying.safeTransferFrom(from, address(this), amount);
      uint256 balanceAfter = IBEP20(underlying).balanceOf(address(this));
      require(balanceAfter.sub(balanceBefore) <= amount);
      return balanceAfter.sub(balanceBefore);
    }
  }

  function _doTransferOut(address to, uint256 amount) private {
    if (underlying == address(ETH)) {
      SafeToken.safeTransferETH(to, amount);
    } else {
      underlying.safeTransfer(to, amount);
    }
  }

  function _redeem(address account, uint256 lAmountIn, uint256 uAmountIn) private returns (uint256) {
    require(lAmountIn == 0 || uAmountIn == 0, "LToken: one of lAmountIn or uAmountIn must be zero");
    require(totalSupply >= lAmountIn, "LToken: not enough total supply");
    require(getCash() >= uAmountIn || uAmountIn == 0, "LToken: not enough underlying");
    require(getCash() >= lAmountIn.mul(exchangeRate()).div(1e18) || lAmountIn == 0, "LToken: not enough underlying");

    uint lAmountToRedeem = lAmountIn > 0 ? lAmountIn : uAmountIn.mul(1e18).div(exchangeRate());
    uint uAmountToRedeem = lAmountIn > 0 ? lAmountIn.mul(exchangeRate()).div(1e18) : uAmountIn;

    require(
      IValidator(core.validator()).redeemAllowed(address(this), account, lAmountToRedeem),
      "LToken: cannot redeem"
    );

    updateSupplyInfo(account, 0, lAmountToRedeem);
    _doTransferOut(account, uAmountToRedeem);

    emit Transfer(account, address(0), lAmountToRedeem);
    emit Redeem(account, uAmountToRedeem, lAmountToRedeem);
    return uAmountToRedeem;
  }

  function _repay(address payer, address borrower, uint256 amount) private returns (uint256) {
    uint256 borrowBalance = borrowBalanceOf(borrower);
    uint256 repayAmount = Math.min(borrowBalance, amount);
    if (borrowBalance.sub(repayAmount) != 0 && borrowBalance.sub(repayAmount) < DUST) {
      repayAmount = borrowBalance.sub(DUST);
    }

    repayAmount = _doTransferIn(payer, repayAmount);
    updateBorrowInfo(borrower, 0, repayAmount);

    if (underlying == address(ETH)) {
      uint256 refundAmount = amount > repayAmount ? amount.sub(repayAmount) : 0;
      if (refundAmount > 0) {
        _doTransferOut(payer, refundAmount);
      }
    }

    emit RepayBorrow(payer, borrower, repayAmount, borrowBalanceOf(borrower));
    return repayAmount;
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

interface IWETH {
  function approve(address spender, uint256 value) external returns (bool);

  function transfer(address recipient, uint256 amount) external returns (bool);

  function deposit() external payable;

  function withdraw(uint256 amount) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "../library/HomoraMath.sol";
import "../interfaces/AggregatorV3Interface.sol";
import "../interfaces/IBEP20.sol";
import "../interfaces/IPriceCalculator.sol";
import "../interfaces/ILToken.sol";

contract PriceCalculator is IPriceCalculator, Ownable {
  using SafeMath for uint256;
  using HomoraMath for uint256;

  // Min price setting interval
  address internal constant ETH = 0x0000000000000000000000000000000000000000;
  uint256 private constant THRESHOLD = 5 minutes;

  /* ========== STATE VARIABLES ========== */

  address public keeper;
  mapping(address => ReferenceData) public references;
  mapping(address => address) private tokenFeeds;

  /* ========== MODIFIERS ========== */

  /// @dev `msg.sender` 가 keeper 또는 owner 인지 검증
  modifier onlyKeeper() {
    require(msg.sender == keeper || msg.sender == owner(), "PriceCalculator: caller is not the owner or keeper");
    _;
  }

  /* ========== INITIALIZER ========== */

  constructor() public {}

  /* ========== RESTRICTED FUNCTIONS ========== */

  /// @notice Keeper address 변경
  /// @dev Keeper address 에서만 요청 가능
  /// @param _keeper New keeper address
  function setKeeper(address _keeper) external onlyKeeper {
    require(_keeper != address(0), "PriceCalculator: invalid keeper address");
    keeper = _keeper;
  }

  /// @notice Chainlink oracle feed 설정
  /// @param asset Asset address to be used as a key
  /// @param feed Chainlink oracle feed contract address
  function setTokenFeed(address asset, address feed) external onlyKeeper {
    tokenFeeds[asset] = feed;
  }

  /// @notice Set price by keeper
  /// @dev Keeper address 에서만 요청 가능
  /// @param assets Array of asset addresses to set
  /// @param prices Array of asset prices to set
  /// @param timestamp Timstamp of price information
  function setPrices(address[] memory assets, uint256[] memory prices, uint256 timestamp) external onlyKeeper {
    require(
      timestamp <= block.timestamp && block.timestamp.sub(timestamp) <= THRESHOLD,
      "PriceCalculator: invalid timestamp"
    );

    for (uint256 i = 0; i < assets.length; i++) {
      references[assets[i]] = ReferenceData({lastData: prices[i], lastUpdated: block.timestamp});
    }
  }

  /* ========== VIEWS ========== */

  function priceOf(address asset) public view override returns (uint256 priceInUSD) {
    if (asset == address(0)) {
      return priceOfETH();
    }
    uint256 decimals = uint256(IBEP20(asset).decimals());
    uint256 unitAmount = 10 ** decimals;
    return _oracleValueInUSDOf(asset, unitAmount, decimals);
  }

  function pricesOf(address[] memory assets) public view override returns (uint256[] memory) {
    uint256[] memory prices = new uint256[](assets.length);
    for (uint256 i = 0; i < assets.length; i++) {
      prices[i] = priceOf(assets[i]);
    }
    return prices;
  }

  function getUnderlyingPrice(address lToken) public view override returns (uint256) {
    return priceOf(ILToken(lToken).underlying());
  }

  function getUnderlyingPrices(address[] memory lTokens) public view override returns (uint256[] memory) {
    uint256[] memory prices = new uint256[](lTokens.length);
    for (uint256 i = 0; i < lTokens.length; i++) {
      prices[i] = priceOf(ILToken(lTokens[i]).underlying());
    }
    return prices;
  }

  function priceOfETH() public view override returns (uint256 valueInUSD) {
    valueInUSD = 0;
    if (tokenFeeds[ETH] != address(0)) {
      (, int256 price, , , ) = AggregatorV3Interface(tokenFeeds[ETH]).latestRoundData();
      return uint256(price).mul(1e10);
    } else if (references[ETH].lastUpdated > block.timestamp.sub(1 days)) {
      return references[ETH].lastData;
    } else {
      revert("PriceCalculator: invalid oracle value");
    }
  }

  /* ========== PRIVATE FUNCTIONS ========== */

  function _oracleValueInUSDOf(
    address asset,
    uint256 amount,
    uint256 decimals
  ) private view returns (uint256 valueInUSD) {
    valueInUSD = 0;
    uint256 assetDecimals = asset == address(0) ? 1e18 : 10 ** decimals;
    if (tokenFeeds[asset] != address(0)) {
      (, int256 price, , , ) = AggregatorV3Interface(tokenFeeds[asset]).latestRoundData();
      valueInUSD = uint256(price).mul(1e10).mul(amount).div(assetDecimals);
    } else if (references[asset].lastUpdated > block.timestamp.sub(1 days)) {
      valueInUSD = references[asset].lastData.mul(amount).div(assetDecimals);
    } else {
      revert("PriceCalculator: invalid oracle value");
    }
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";

library HomoraMath {
  using SafeMath for uint256;

  function divCeil(uint256 lhs, uint256 rhs) internal pure returns (uint256) {
    return lhs.add(rhs).sub(1) / rhs;
  }

  function fmul(uint256 lhs, uint256 rhs) internal pure returns (uint256) {
    return lhs.mul(rhs) / (2 ** 112);
  }

  function fdiv(uint256 lhs, uint256 rhs) internal pure returns (uint256) {
    return lhs.mul(2 ** 112) / rhs;
  }

  // implementation from https://github.com/Uniswap/uniswap-lib/commit/99f3f28770640ba1bb1ff460ac7c5292fb8291a0
  // original implementation: https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol#L687
  function sqrt(uint256 x) internal pure returns (uint256) {
    if (x == 0) return 0;
    uint256 xx = x;
    uint256 r = 1;

    if (xx >= 0x100000000000000000000000000000000) {
      xx >>= 128;
      r <<= 64;
    }

    if (xx >= 0x10000000000000000) {
      xx >>= 64;
      r <<= 32;
    }
    if (xx >= 0x100000000) {
      xx >>= 32;
      r <<= 16;
    }
    if (xx >= 0x10000) {
      xx >>= 16;
      r <<= 8;
    }
    if (xx >= 0x100) {
      xx >>= 8;
      r <<= 4;
    }
    if (xx >= 0x10) {
      xx >>= 4;
      r <<= 2;
    }
    if (xx >= 0x8) {
      r <<= 1;
    }

    r = (r + x / r) >> 1;
    r = (r + x / r) >> 1;
    r = (r + x / r) >> 1;
    r = (r + x / r) >> 1;
    r = (r + x / r) >> 1;
    r = (r + x / r) >> 1;
    r = (r + x / r) >> 1; // Seven iterations should be enough
    uint256 r1 = x / r;
    return (r < r1 ? r : r1);
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.6.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  // getRoundData and latestRoundData should both raise "No data present"
  // if they do not have data to report, instead of returning unset values
  // which could be misinterpreted as actual reported values.
  function getRoundData(
    uint80 _roundId
  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

  function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

import "./interfaces/IULABMigration.sol";
import "./interfaces/IBEP20.sol";

import "./library/SafeToken.sol";

contract ULABMigration is Ownable, ReentrancyGuard, Pausable, IULABMigration {
    using SafeMath for uint256;
    using SafeToken for address;

    /* ========== CONSTANT VARIABLES ========== */

    address public constant DEAD_ADDR = 0x000000000000000000000000000000000000dEaD;

    /* ========== STATE VARIABLES ========== */

    // ULAB token
    address public ulabToken;
    // LAB token
    address public labToken;

    // Migration start time
    uint256 public startTime;
    // Migration end time
    uint256 public endTime;

    uint256 public exchangeRate;

    // initializer
    bool public initialized;

    /* ========== INITIALIZER ========== */

    constructor() public {}

    function initialize(
        address _ulabToken,
        address _labToken,
        uint256 _startTime,
        uint256 _endTime,
        uint256 _exchangeRate
    ) external onlyOwner {
        require(initialized == false, "already initialized");
        require(_ulabToken != address(0), "invalid ulab address");
        require(_labToken != address(0), "invalid lab address");
        require(_startTime > 0, "invalid start time");
        require(_endTime > 0, "invalid end time");
        require(_exchangeRate > 0, "invalid exchange rate");

        ulabToken = _ulabToken;
        labToken = _labToken;
        startTime = _startTime;
        endTime = _endTime;
        exchangeRate = _exchangeRate;

        initialized = true;
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

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

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

    function withdrawUlabToken() external onlyOwner {
        uint256 _balance = IBEP20(ulabToken).balanceOf(address(this));
        ulabToken.safeTransfer(msg.sender, _balance);
    }

    /* ========== MUTATIVE FUNCTIONS ========== */

    function migrate(uint256 _amount) external override nonReentrant whenNotPaused {
        require(block.timestamp >= startTime && block.timestamp <= endTime, "not migration time");
        require(_amount > 0, "amount must be higher than 0");

        uint256 _burnAmount = _amount;
        // burn legacy token
        labToken.safeTransferFrom(msg.sender, DEAD_ADDR, _burnAmount);

        uint256 _newTokenAmount = _burnAmount.mul(exchangeRate).div(1e18);
        // transfer new token
        ulabToken.safeTransfer(msg.sender, _newTokenAmount);

        emit Migrate(msg.sender, _burnAmount, _newTokenAmount);
    }

    /* ========== VIEWS ========== */

    function ulabTokenAmount(uint256 _amount) external override view returns (uint256) {
        uint256 _ulabTokenAmount = _amount.mul(exchangeRate).div(1e18);
        return _ulabTokenAmount;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

interface IULABMigration {
    event Migrate(address indexed user, uint256 labAmount, uint256 ulabAmount);

    function migrate(uint256 _amount) external;
    function ulabTokenAmount(uint256 _amount) external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/math/SafeMath.sol";

import "../interfaces/ISaleLabDashboard.sol";
import "../interfaces/ISaleLabOverflowFarm.sol";

contract SaleLabDashboard is ISaleLabDashboard {
    using SafeMath for uint256;

    ISaleLabOverflowFarm public saleLabOverflowFarm;

    constructor(address _saleLabOverflowFarm) public {
        saleLabOverflowFarm = ISaleLabOverflowFarm(_saleLabOverflowFarm);
    }

    function getOverflowFarmUserInfo(address user) external view override returns (OverflowFarmUserInfo memory) {
        address _user = user;

        (uint256 _totalPurchasedETH, bool _claimed, ) = saleLabOverflowFarm.userInfo(_user);

        uint256 _refundETH = saleLabOverflowFarm.getRefundingAmount(_user);
        bool _isWhitelist = saleLabOverflowFarm.whitelist(_user);

        uint256 _purchasedLAB = saleLabOverflowFarm.getOfferingAmount(_user);
        uint256 _farmingRewardAmount = saleLabOverflowFarm.pendingReward(_user);

        OverflowFarmUserInfo memory overflowFarmInfo = OverflowFarmUserInfo({
            totalPurchasedETH: _totalPurchasedETH,
            refundETH: _refundETH,
            purchasedLAB: _purchasedLAB,
            farmingRewardAmount: _farmingRewardAmount,
            isWhitelist: _isWhitelist,
            claimed: _claimed
        });

        return overflowFarmInfo;
    }

    function getOverflowFarmInfo() external view override returns (OverflowFarmInfo memory) {
        uint256 _offeringAmount = saleLabOverflowFarm.offeringAmount();
        uint256 _raisingAmount = saleLabOverflowFarm.raisingAmount();

        uint256 _labPriceInETH = 0;
        if (_raisingAmount == 0) {
            _labPriceInETH = uint256(1e17).mul(1e18).div(_offeringAmount);
        } else {
            _labPriceInETH = _raisingAmount.mul(1e18).div(_offeringAmount);
        }

        uint256 _rewardPerSecond = saleLabOverflowFarm.rewardPerSecond();
        uint256 _dayReward = _rewardPerSecond.mul(86400);
        uint256 _totalSupply = saleLabOverflowFarm.totalAmount();

        if (_totalSupply == 0) {
            _totalSupply = 1e18;
        }

        uint256 _rewardInETH = _labPriceInETH.mul(_dayReward).div(1e18);
        uint256 _stakedInETH = _totalSupply;

        uint256 _dayProfitInETH = _rewardInETH.mul(1e18).div(_stakedInETH);
        uint256 _apr = _dayProfitInETH.mul(365);

        OverflowFarmInfo memory overflowFarmInfo = OverflowFarmInfo({
            raisingAmount: _raisingAmount,
            totalAmount: saleLabOverflowFarm.totalAmount(),
            labPriceInETH: _labPriceInETH,
            startTime: saleLabOverflowFarm.startTime(),
            endTime: saleLabOverflowFarm.endTime(),
            farmingRewardAPR: _apr,
            harvestTime: saleLabOverflowFarm.harvestTimestamp()
        });
        return overflowFarmInfo;
    }
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

interface ISaleLabDashboard {
    struct OverflowFarmUserInfo {
        uint256 totalPurchasedETH;
        uint256 refundETH;
        uint256 purchasedLAB;
        uint256 farmingRewardAmount;
        bool isWhitelist;
        bool claimed;
    }

    struct OverflowFarmInfo {
        uint256 raisingAmount; // target raising amount
        uint256 totalAmount; // total raised amount
        uint256 labPriceInETH;
        uint256 startTime;
        uint256 endTime;
        uint256 farmingRewardAPR;
        uint256 harvestTime;
    }

    function getOverflowFarmUserInfo(address user) external view returns (OverflowFarmUserInfo memory);
    function getOverflowFarmInfo() external view returns (OverflowFarmInfo memory);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.0;

import "../interfaces/IBEP20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

abstract contract BEP20 is IBEP20, Ownable {
  using SafeMath for uint256;

  mapping(address => uint256) private _balances;
  mapping(address => mapping(address => uint256)) internal _allowances;
  uint256 private _totalSupply;
  string private _name;
  string private _symbol;
  uint8 private _decimals;

  uint256[50] private __gap;

  /**
   * @dev sets initials supply and the owner
   */
  function __BEP20__init(string memory name, string memory symbol, uint8 decimals) internal {
    _name = name;
    _symbol = symbol;
    _decimals = decimals;
  }

  /**
   * @dev Returns the bep token owner.
   */
  function getOwner() external view override returns (address) {
    return owner();
  }

  /**
   * @dev Returns the token decimals.
   */
  function decimals() external view override returns (uint8) {
    return _decimals;
  }

  /**
   * @dev Returns the token symbol.
   */
  function symbol() external view override returns (string memory) {
    return _symbol;
  }

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

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

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

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

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

  /**
   * @dev See {BEP20-approve}.
   *
   * Requirements:
   *
   * - `spender` cannot be the zero address.
   */
  function approve(address spender, uint256 amount) external override returns (bool) {
    _approve(_msgSender(), spender, amount);
    return true;
  }

  /**
   * @dev See {BEP20-transferFrom}.
   *
   * Emits an {Approval} event indicating the updated allowance. This is not
   * required by the EIP. See the note at the beginning of {BEP20};
   *
   * Requirements:
   * - `sender` and `recipient` cannot be the zero address.
   * - `sender` must have a balance of at least `amount`.
   * - the caller must have allowance for `sender`'s tokens of at least
   * `amount`.
   */
  function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) {
    _transfer(sender, recipient, amount);
    _approve(
      sender,
      _msgSender(),
      _allowances[sender][_msgSender()].sub(amount, "BEP20: transfer amount exceeds allowance")
    );
    return true;
  }

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

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

  /**
   * @dev Burn `amount` tokens and decreasing the total supply.
   */
  function burn(uint256 amount) public returns (bool) {
    _burn(_msgSender(), amount);
    return true;
  }

  /**
   * @dev Moves tokens `amount` from `sender` to `recipient`.
   *
   * This is internal function is equivalent to {transfer}, and can be used to
   * e.g. implement automatic token fees, slashing mechanisms, etc.
   *
   * Emits a {Transfer} event.
   *
   * Requirements:
   *
   * - `sender` cannot be the zero address.
   * - `recipient` cannot be the zero address.
   * - `sender` must have a balance of at least `amount`.
   */
  function _transfer(address sender, address recipient, uint256 amount) internal {
    require(sender != address(0), "BEP20: transfer from the zero address");
    require(recipient != address(0), "BEP20: transfer to the zero address");

    _balances[sender] = _balances[sender].sub(amount, "BEP20: transfer amount exceeds balance");
    _balances[recipient] = _balances[recipient].add(amount);
    emit Transfer(sender, recipient, amount);
  }

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

    _totalSupply = _totalSupply.add(amount);
    _balances[account] = _balances[account].add(amount);
    emit Transfer(address(0), account, amount);
  }

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

    _balances[account] = _balances[account].sub(amount, "BEP20: burn amount exceeds balance");
    _totalSupply = _totalSupply.sub(amount);
    emit Transfer(account, address(0), amount);
  }

  /**
   * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
   *
   * This is internal function is equivalent to `approve`, and can be used to
   * e.g. set automatic allowances for certain subsystems, etc.
   *
   * Emits an {Approval} event.
   *
   * Requirements:
   *
   * - `owner` cannot be the zero address.
   * - `spender` cannot be the zero address.
   */
  function _approve(address owner, address spender, uint256 amount) internal {
    require(owner != address(0), "BEP20: approve from the zero address");
    require(spender != address(0), "BEP20: approve to the zero address");

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

  /**
   * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
   * from the caller's allowance.
   *
   * See {_burn} and {_approve}.
   */
  function _burnFrom(address account, uint256 amount) internal {
    _burn(account, amount);
    _approve(
      account,
      _msgSender(),
      _allowances[account][_msgSender()].sub(amount, "BEP20: burn amount exceeds allowance")
    );
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract LABToken is Ownable, ERC20("LayerBank Token", "LAB.s") {
  /* ========== STATE VARIABLES ========== */

  mapping(address => bool) private _minters;

  /* ========== MODIFIERS ========== */

  modifier onlyMinter() {
    require(isMinter(msg.sender), "LAB: caller is not the minter");
    _;
  }

  /* ========== INITIALIZER ========== */

  constructor() public {
    _minters[owner()] = true;
  }

  /* ========== RESTRICTED FUNCTIONS ========== */

  function setMinter(address minter, bool canMint) external onlyOwner {
    _minters[minter] = canMint;
  }

  function mint(address _to, uint256 _amount) public onlyMinter {
    _mint(_to, _amount);
  }

  /* ========== VIEWS ========== */

  function isMinter(address account) public view returns (bool) {
    return _minters[account];
  }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";

library SafeDecimalMath {
  using SafeMath for uint256;

  /* Number of decimal places in the representations. */
  uint256 private constant decimals = 18;
  uint256 private constant highPrecisionDecimals = 27;

  /* The number representing 1.0. */
  uint256 private constant UNIT = 10 ** uint256(decimals);

  /* The number representing 1.0 for higher fidelity numbers. */
  uint256 private constant PRECISE_UNIT = 10 ** uint256(highPrecisionDecimals);
  uint256 private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10 ** uint256(highPrecisionDecimals - decimals);

  /**
   * @return The result of multiplying x and y, interpreting the operands as fixed-point
   * decimals.
   *
   * @dev A unit factor is divided out after the product of x and y is evaluated,
   * so that product must be less than 2**256. As this is an integer division,
   * the internal division always rounds down. This helps save on gas. Rounding
   * is more expensive on gas.
   */
  function multiplyDecimal(uint256 x, uint256 y) internal pure returns (uint256) {
    /* Divide by UNIT to remove the extra factor introduced by the product. */
    return x.mul(y).div(UNIT);
  }

  function multiplyDecimalPrecise(uint256 x, uint256 y) internal pure returns (uint256) {
    /* Divide by UNIT to remove the extra factor introduced by the product. */
    return x.mul(y).div(PRECISE_UNIT);
  }

  /**
   * @return The result of safely dividing x and y. The return value is a high
   * precision decimal.
   *
   * @dev y is divided after the product of x and the standard precision unit
   * is evaluated, so the product of x and UNIT must be less than 2**256. As
   * this is an integer division, the result is always rounded down.
   * This helps save on gas. Rounding is more expensive on gas.
   */
  function divideDecimal(uint256 x, uint256 y) internal pure returns (uint256) {
    /* Reintroduce the UNIT factor that will be divided out by y. */
    return x.mul(UNIT).div(y);
  }

  function divideDecimalPrecise(uint256 x, uint256 y) internal pure returns (uint256) {
    /* Reintroduce the UNIT factor that will be divided out by y. */
    return x.mul(PRECISE_UNIT).div(y);
  }

  /**
   * @dev Convert a standard decimal representation to a high precision one.
   */
  function decimalToPreciseDecimal(uint256 i) internal pure returns (uint256) {
    return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);
  }

  /**
   * @dev Convert a high precision decimal to a standard decimal representation.
   */
  function preciseDecimalToDecimal(uint256 i) internal pure returns (uint256) {
    uint256 quotientTimesTen = i.mul(10).div(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);

    if (quotientTimesTen % 10 >= 5) {
      quotientTimesTen = quotientTimesTen.add(10);
    }

    return quotientTimesTen.div(10);
  }

  /**
   * @dev Returns the multiplication of two unsigned integers, and the max value of
   * uint256 on overflow.
   */
  function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    return c / a != b ? type(uint256).max : c;
  }

  function saturatingMultiplyDecimal(uint256 x, uint256 y) internal pure returns (uint256) {
    /* Divide by UNIT to remove the extra factor introduced by the product. */
    return saturatingMul(x, y).div(UNIT);
  }
}

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

Contract Security Audit

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"address","name":"lToken","type":"address"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"borrowAmount","type":"uint256"}],"name":"borrowAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"core","outputs":[{"internalType":"contract ICore","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getAccountLiquidity","outputs":[{"internalType":"uint256","name":"collateralInUSD","type":"uint256"},{"internalType":"uint256","name":"supplyInUSD","type":"uint256"},{"internalType":"uint256","name":"borrowInUSD","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_lab","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lTokenBorrowed","type":"address"},{"internalType":"address","name":"lTokenCollateral","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"lTokenAmountToSeize","outputs":[{"internalType":"uint256","name":"seizeLAmount","type":"uint256"},{"internalType":"uint256","name":"rebateLAmount","type":"uint256"},{"internalType":"uint256","name":"liquidatorLAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"lToken","type":"address"},{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"liquidateAmount","type":"uint256"},{"internalType":"uint256","name":"closeFactor","type":"uint256"}],"name":"liquidateAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"oracle","outputs":[{"internalType":"contract IPriceCalculator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lToken","type":"address"},{"internalType":"address","name":"redeemer","type":"address"},{"internalType":"uint256","name":"redeemAmount","type":"uint256"}],"name":"redeemAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_core","type":"address"}],"name":"setCore","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_priceCalculator","type":"address"}],"name":"setPriceCalculator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]

608060405234801561001057600080fd5b50600061001b61006a565b600080546001600160a01b0319166001600160a01b0383169081178255604051929350917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a35061006e565b3390565b6120a78061007d6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638da5cb5b1161008c578063eabe7d9111610066578063eabe7d91146101b5578063f2f4eb26146101c8578063f2fde38b146101d0578063f643b5df146101e3576100ea565b80638da5cb5b14610187578063c4d66de81461018f578063da3d454c146101a2576100ea565b80636922d7b6116100c85780636922d7b614610142578063715018a6146101575780637dc0d1d01461015f5780638000963014610174576100ea565b8063158ef93e146100ef57806325d024621461010d5780635ec88c7914610120575b600080fd5b6100f76101f6565b6040516101049190611bdb565b60405180910390f35b6100f761011b366004611915565b610206565b61013361012e36600461189d565b6102f0565b60405161010493929190611fee565b61015561015036600461189d565b610758565b005b6101556107df565b610167610868565b6040516101049190611b60565b61015561018236600461189d565b610877565b610167610927565b61015561019d36600461189d565b610936565b6100f76101b03660046118d5565b6109cf565b6100f76101c33660046118d5565b610c7d565b610167610c98565b6101556101de36600461189d565b610ca7565b6101336101f13660046118d5565b610d67565b600354600160a01b900460ff1681565b600080610217856000806000611117565b915050806102405760405162461bcd60e51b815260040161023790611e85565b60405180910390fd5b6040516341cce05d60e11b81526000906001600160a01b03881690638399c0ba9061026f908990600401611b60565b602060405180830381600087803b15801561028957600080fd5b505af115801561029d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c19190611b27565b905060006102e1670de0b6b3a76400006102db8785611652565b90611695565b90951115979650505050505050565b6002546040516305189e1160e41b8152600091829182916060916001600160a01b0390911690635189e1109061032a908890600401611b60565b60006040518083038186803b15801561034257600080fd5b505afa158015610356573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261037e919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906103b4908590600401611b8e565b60006040518083038186803b1580156103cc57600080fd5b505afa1580156103e0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261040891908101906119f8565b905060005b825181101561074e5781818151811061042257fe5b60200260200101516000141561044a5760405162461bcd60e51b815260040161023790611be6565b600061046884838151811061045b57fe5b60200260200101516116c7565b905061047261187c565b84838151811061047e57fe5b60200260200101516001600160a01b031663014a296f8a6040518263ffffffff1660e01b81526004016104b19190611b60565b60606040518083038186803b1580156104c957600080fd5b505afa1580156104dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105019190611a98565b60035486519192506000916001600160a01b039091169087908690811061052457fe5b60200260200101516001600160a01b031614801561055c575067010a741a4627800085858151811061055257fe5b6020026020010151115b15610570575067010a741a46278000610587565b84848151811061057c57fe5b602002602001015190505b60025486516000916001600160a01b031690636e8584fd908990889081106105ab57fe5b60200260200101516040518263ffffffff1660e01b81526004016105cf9190611b60565b60806040518083038186803b1580156105e757600080fd5b505afa1580156105fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061f9190611ad5565b606001519050600061065b6ec097ce7bc90715b34b9f10000000006102db8461065587896040015161165290919063ffffffff16565b90611652565b905061069461068d670de0b6b3a76400006102db846106558a601203600a0a8a6000015161165290919063ffffffff16565b8c906117d1565b9a506106ef6106e86ec097ce7bc90715b34b9f10000000006102db8a8a815181106106bb57fe5b60200260200101516106558a601203600a0a6106558b604001518c6000015161165290919063ffffffff16565b8b906117d1565b995061073b610734670de0b6b3a76400006102db8a8a8151811061070f57fe5b60200260200101516106558a601203600a0a8a6020015161165290919063ffffffff16565b8a906117d1565b9850506001909401935061040d92505050565b5050509193909250565b6107606117f6565b6001600160a01b0316610771610927565b6001600160a01b0316146107975760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166107bd5760405162461bcd60e51b815260040161023790611d5e565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6107e76117f6565b6001600160a01b03166107f8610927565b6001600160a01b03161461081e5760405162461bcd60e51b815260040161023790611f3e565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6001546001600160a01b031681565b61087f6117f6565b6001600160a01b0316610890610927565b6001600160a01b0316146108b65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166108dc5760405162461bcd60e51b815260040161023790611fb7565b6002546001600160a01b0316156109055760405162461bcd60e51b815260040161023790611ec6565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031690565b61093e6117f6565b6001600160a01b031661094f610927565b6001600160a01b0316146109755760405162461bcd60e51b815260040161023790611f3e565b600354600160a01b900460ff161561099f5760405162461bcd60e51b815260040161023790611e16565b6003805460ff60a01b196001600160a01b039093166001600160a01b03199091161791909116600160a01b179055565b60006103e882116109f25760405162461bcd60e51b815260040161023790611e43565b60025460405163929fe9a160e01b81526001600160a01b039091169063929fe9a190610a249086908890600401611b74565b60206040518083038186803b158015610a3c57600080fd5b505afa158015610a50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a749190611a7c565b610a905760405162461bcd60e51b815260040161023790611d27565b60015460405163fc57d4df60e01b81526000916001600160a01b03169063fc57d4df90610ac1908890600401611b60565b60206040518083038186803b158015610ad957600080fd5b505afa158015610aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b119190611b27565b11610b2e5760405162461bcd60e51b815260040161023790611c16565b600254604051636e8584fd60e01b81526000916001600160a01b031690636e8584fd90610b5f908890600401611b60565b60806040518083038186803b158015610b7757600080fd5b505afa158015610b8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610baf9190611ad5565b6040015190508015610c62576000856001600160a01b031663ecdaff7e6040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610bf857600080fd5b505af1158015610c0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c309190611b27565b90506000610c3e82866117d1565b9050828110610c5f5760405162461bcd60e51b815260040161023790611f73565b50505b6000610c718587600087611117565b15979650505050505050565b600080610c8d8486856000611117565b159695505050505050565b6002546001600160a01b031681565b610caf6117f6565b6001600160a01b0316610cc0610927565b6001600160a01b031614610ce65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b038116610d0c5760405162461bcd60e51b815260040161023790611c57565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60015460405163fc57d4df60e01b8152600091829182916001600160a01b03169063fc57d4df90610d9c908990600401611b60565b60206040518083038186803b158015610db457600080fd5b505afa158015610dc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dec9190611b27565b15801590610e78575060015460405163fc57d4df60e01b81526001600160a01b039091169063fc57d4df90610e25908890600401611b60565b60206040518083038186803b158015610e3d57600080fd5b505afa158015610e51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e759190611b27565b15155b610e945760405162461bcd60e51b815260040161023790611be6565b6000856001600160a01b0316638b9db0376040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610ed157600080fd5b505af1158015610ee5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f099190611b27565b905080610f285760405162461bcd60e51b815260040161023790611cd4565b6000610f33886116c7565b90506000610f40886116c7565b60015460405163fc57d4df60e01b81529192506000916110f191610fce9187916001600160a01b03169063fc57d4df90610f7e908f90600401611b60565b60206040518083038186803b158015610f9657600080fd5b505afa158015610faa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106559190611b27565b6102db600160009054906101000a90046001600160a01b03166001600160a01b031663fc57d4df8e6040518263ffffffff1660e01b81526004016110129190611b60565b60206040518083038186803b15801561102a57600080fd5b505afa15801561103e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110629190611b27565b6002546040805163231d97a560e21b81529051610655926001600160a01b031691638c765e94916004808301926020929190829003018186803b1580156110a857600080fd5b505afa1580156110bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e09190611b27565b6106558e60128b9003600a0a611652565b9050611104816012849003600a0a611695565b9a60009a508b9950975050505050505050565b6002546040516305189e1160e41b81526000918291829182916060916001600160a01b031690635189e11090611151908c90600401611b60565b60006040518083038186803b15801561116957600080fd5b505afa15801561117d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111a5919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906111db908590600401611b8e565b60006040518083038186803b1580156111f357600080fd5b505afa158015611207573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261122f91908101906119f8565b905060005b825181101561160f57600061124e84838151811061045b57fe5b905082828151811061125c57fe5b6020026020010151600014156112845760405162461bcd60e51b815260040161023790611be6565b61128c61187c565b84838151811061129857fe5b60200260200101516001600160a01b03166392fa4e8e8e6040518263ffffffff1660e01b81526004016112cb9190611b60565b606060405180830381600087803b1580156112e557600080fd5b505af11580156112f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131d9190611a98565b60035486519192506000916001600160a01b039091169087908690811061134057fe5b60200260200101516001600160a01b0316148015611378575067010a741a4627800085858151811061136e57fe5b6020026020010151115b1561144d576002548651611446916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a9081106113b757fe5b60200260200101516040518263ffffffff1660e01b81526004016113db9190611b60565b60806040518083038186803b1580156113f357600080fd5b505afa158015611407573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142b9190611ad5565b6060015160408601516106559067010a741a46278000611652565b905061152c565b6002548651611529916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a90811061148757fe5b60200260200101516040518263ffffffff1660e01b81526004016114ab9190611b60565b60806040518083038186803b1580156114c357600080fd5b505afa1580156114d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114fb9190611ad5565b6060015161065589898151811061150e57fe5b6020026020010151876040015161165290919063ffffffff16565b90505b61156361155c670de0b6b3a76400006102db8461065588601203600a0a886000015161165290919063ffffffff16565b89906117d1565b97506115af6115a8670de0b6b3a76400006102db88888151811061158357fe5b602002602001015161065588601203600a0a886020015161165290919063ffffffff16565b88906117d1565b96508c6001600160a01b03168685815181106115c757fe5b60200260200101516001600160a01b03161415611604576116016115a88d8d848989815181106115f357fe5b6020026020010151886117fa565b96505b505050600101611234565b5082841161161e576000611628565b6116288484611854565b95508284116116405761163b8385611854565b611643565b60005b94505050505094509492505050565b6000826116615750600061168f565b8282028284828161166e57fe5b041461168c5760405162461bcd60e51b815260040161023790611efd565b90505b92915050565b60008082116116b65760405162461bcd60e51b815260040161023790611ddf565b8183816116bf57fe5b049392505050565b600080826001600160a01b0316636f307dc36040518163ffffffff1660e01b815260040160206040518083038186803b15801561170357600080fd5b505afa158015611717573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173b91906118b9565b90506001600160a01b03811661175457601291506117cb565b806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561178d57600080fd5b505afa1580156117a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117c59190611b3f565b60ff1691505b50919050565b60008282018381101561168c5760405162461bcd60e51b815260040161023790611c9d565b3390565b600061181d670de0b6b3a76400006102db866106558a6012889003600a0a611652565b905061184a611843670de0b6b3a76400006102db866106558a6012899003600a0a611652565b82906117d1565b9695505050505050565b6000828211156118765760405162461bcd60e51b815260040161023790611da8565b50900390565b60405180606001604052806000815260200160008152602001600081525090565b6000602082840312156118ae578081fd5b813561168c8161204b565b6000602082840312156118ca578081fd5b815161168c8161204b565b6000806000606084860312156118e9578182fd5b83356118f48161204b565b925060208401356119048161204b565b929592945050506040919091013590565b6000806000806080858703121561192a578081fd5b84356119358161204b565b935060208501356119458161204b565b93969395505050506040820135916060013590565b6000602080838503121561196c578182fd5b825167ffffffffffffffff811115611982578283fd5b8301601f81018513611992578283fd5b80516119a56119a08261202b565b612004565b81815283810190838501858402850186018910156119c1578687fd5b8694505b838510156119ec5780516119d88161204b565b8352600194909401939185019185016119c5565b50979650505050505050565b60006020808385031215611a0a578182fd5b825167ffffffffffffffff811115611a20578283fd5b8301601f81018513611a30578283fd5b8051611a3e6119a08261202b565b8181528381019083850185840285018601891015611a5a578687fd5b8694505b838510156119ec578051835260019490940193918501918501611a5e565b600060208284031215611a8d578081fd5b815161168c81612063565b600060608284031215611aa9578081fd5b611ab36060612004565b8251815260208301516020820152604083015160408201528091505092915050565b600060808284031215611ae6578081fd5b611af06080612004565b8251611afb81612063565b808252506020830151602082015260408301516040820152606083015160608201528091505092915050565b600060208284031215611b38578081fd5b5051919050565b600060208284031215611b50578081fd5b815160ff8116811461168c578182fd5b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015611bcf5783516001600160a01b031683529284019291840191600101611baa565b50909695505050505050565b901515815260200190565b6020808252601690820152752b30b634b230ba37b91d10383934b1b29032b93937b960511b604082015260600190565b60208082526021908201527f56616c696461746f723a20556e6465726c79696e67207072696365206572726f6040820152603960f91b606082015260800190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526033908201527f56616c696461746f723a2065786368616e676552617465206f66206c546f6b656040820152726e436f6c6c61746572616c206973207a65726f60681b606082015260800190565b6020808252601f908201527f56616c696461746f723a20656e7465724d61726b657420726571756972656400604082015260600190565b6020808252602a908201527f56616c696461746f723a20696e76616c696420707269636543616c63756c61746040820152696f72206164647265737360b01b606082015260800190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b602080825260139082015272185b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604082015260600190565b60208082526022908201527f56616c696461746f723a20746f6f20736d616c6c20626f72726f7720616d6f756040820152611b9d60f21b606082015260800190565b60208082526021908201527f56616c696461746f723a20496e73756666696369656e742073686f727466616c6040820152601b60fa1b606082015260800190565b6020808252601b908201527f56616c696461746f723a20636f726520616c7265616479207365740000000000604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526024908201527f56616c696461746f723a206d61726b657420626f72726f77206361702072656160408201526318da195960e21b606082015260800190565b6020808252601f908201527f56616c696461746f723a20696e76616c696420636f7265206164647265737300604082015260600190565b9283526020830191909152604082015260600190565b60405181810167ffffffffffffffff8111828210171561202357600080fd5b604052919050565b600067ffffffffffffffff821115612041578081fd5b5060209081020190565b6001600160a01b038116811461206057600080fd5b50565b801515811461206057600080fdfea26469706673582212203a341cb9fdf943e35fd9d0c8e789e0d69ee7c11ae267c21da988555add48c88064736f6c634300060c0033

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638da5cb5b1161008c578063eabe7d9111610066578063eabe7d91146101b5578063f2f4eb26146101c8578063f2fde38b146101d0578063f643b5df146101e3576100ea565b80638da5cb5b14610187578063c4d66de81461018f578063da3d454c146101a2576100ea565b80636922d7b6116100c85780636922d7b614610142578063715018a6146101575780637dc0d1d01461015f5780638000963014610174576100ea565b8063158ef93e146100ef57806325d024621461010d5780635ec88c7914610120575b600080fd5b6100f76101f6565b6040516101049190611bdb565b60405180910390f35b6100f761011b366004611915565b610206565b61013361012e36600461189d565b6102f0565b60405161010493929190611fee565b61015561015036600461189d565b610758565b005b6101556107df565b610167610868565b6040516101049190611b60565b61015561018236600461189d565b610877565b610167610927565b61015561019d36600461189d565b610936565b6100f76101b03660046118d5565b6109cf565b6100f76101c33660046118d5565b610c7d565b610167610c98565b6101556101de36600461189d565b610ca7565b6101336101f13660046118d5565b610d67565b600354600160a01b900460ff1681565b600080610217856000806000611117565b915050806102405760405162461bcd60e51b815260040161023790611e85565b60405180910390fd5b6040516341cce05d60e11b81526000906001600160a01b03881690638399c0ba9061026f908990600401611b60565b602060405180830381600087803b15801561028957600080fd5b505af115801561029d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c19190611b27565b905060006102e1670de0b6b3a76400006102db8785611652565b90611695565b90951115979650505050505050565b6002546040516305189e1160e41b8152600091829182916060916001600160a01b0390911690635189e1109061032a908890600401611b60565b60006040518083038186803b15801561034257600080fd5b505afa158015610356573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261037e919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906103b4908590600401611b8e565b60006040518083038186803b1580156103cc57600080fd5b505afa1580156103e0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261040891908101906119f8565b905060005b825181101561074e5781818151811061042257fe5b60200260200101516000141561044a5760405162461bcd60e51b815260040161023790611be6565b600061046884838151811061045b57fe5b60200260200101516116c7565b905061047261187c565b84838151811061047e57fe5b60200260200101516001600160a01b031663014a296f8a6040518263ffffffff1660e01b81526004016104b19190611b60565b60606040518083038186803b1580156104c957600080fd5b505afa1580156104dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105019190611a98565b60035486519192506000916001600160a01b039091169087908690811061052457fe5b60200260200101516001600160a01b031614801561055c575067010a741a4627800085858151811061055257fe5b6020026020010151115b15610570575067010a741a46278000610587565b84848151811061057c57fe5b602002602001015190505b60025486516000916001600160a01b031690636e8584fd908990889081106105ab57fe5b60200260200101516040518263ffffffff1660e01b81526004016105cf9190611b60565b60806040518083038186803b1580156105e757600080fd5b505afa1580156105fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061f9190611ad5565b606001519050600061065b6ec097ce7bc90715b34b9f10000000006102db8461065587896040015161165290919063ffffffff16565b90611652565b905061069461068d670de0b6b3a76400006102db846106558a601203600a0a8a6000015161165290919063ffffffff16565b8c906117d1565b9a506106ef6106e86ec097ce7bc90715b34b9f10000000006102db8a8a815181106106bb57fe5b60200260200101516106558a601203600a0a6106558b604001518c6000015161165290919063ffffffff16565b8b906117d1565b995061073b610734670de0b6b3a76400006102db8a8a8151811061070f57fe5b60200260200101516106558a601203600a0a8a6020015161165290919063ffffffff16565b8a906117d1565b9850506001909401935061040d92505050565b5050509193909250565b6107606117f6565b6001600160a01b0316610771610927565b6001600160a01b0316146107975760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166107bd5760405162461bcd60e51b815260040161023790611d5e565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6107e76117f6565b6001600160a01b03166107f8610927565b6001600160a01b03161461081e5760405162461bcd60e51b815260040161023790611f3e565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6001546001600160a01b031681565b61087f6117f6565b6001600160a01b0316610890610927565b6001600160a01b0316146108b65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166108dc5760405162461bcd60e51b815260040161023790611fb7565b6002546001600160a01b0316156109055760405162461bcd60e51b815260040161023790611ec6565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031690565b61093e6117f6565b6001600160a01b031661094f610927565b6001600160a01b0316146109755760405162461bcd60e51b815260040161023790611f3e565b600354600160a01b900460ff161561099f5760405162461bcd60e51b815260040161023790611e16565b6003805460ff60a01b196001600160a01b039093166001600160a01b03199091161791909116600160a01b179055565b60006103e882116109f25760405162461bcd60e51b815260040161023790611e43565b60025460405163929fe9a160e01b81526001600160a01b039091169063929fe9a190610a249086908890600401611b74565b60206040518083038186803b158015610a3c57600080fd5b505afa158015610a50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a749190611a7c565b610a905760405162461bcd60e51b815260040161023790611d27565b60015460405163fc57d4df60e01b81526000916001600160a01b03169063fc57d4df90610ac1908890600401611b60565b60206040518083038186803b158015610ad957600080fd5b505afa158015610aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b119190611b27565b11610b2e5760405162461bcd60e51b815260040161023790611c16565b600254604051636e8584fd60e01b81526000916001600160a01b031690636e8584fd90610b5f908890600401611b60565b60806040518083038186803b158015610b7757600080fd5b505afa158015610b8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610baf9190611ad5565b6040015190508015610c62576000856001600160a01b031663ecdaff7e6040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610bf857600080fd5b505af1158015610c0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c309190611b27565b90506000610c3e82866117d1565b9050828110610c5f5760405162461bcd60e51b815260040161023790611f73565b50505b6000610c718587600087611117565b15979650505050505050565b600080610c8d8486856000611117565b159695505050505050565b6002546001600160a01b031681565b610caf6117f6565b6001600160a01b0316610cc0610927565b6001600160a01b031614610ce65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b038116610d0c5760405162461bcd60e51b815260040161023790611c57565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60015460405163fc57d4df60e01b8152600091829182916001600160a01b03169063fc57d4df90610d9c908990600401611b60565b60206040518083038186803b158015610db457600080fd5b505afa158015610dc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dec9190611b27565b15801590610e78575060015460405163fc57d4df60e01b81526001600160a01b039091169063fc57d4df90610e25908890600401611b60565b60206040518083038186803b158015610e3d57600080fd5b505afa158015610e51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e759190611b27565b15155b610e945760405162461bcd60e51b815260040161023790611be6565b6000856001600160a01b0316638b9db0376040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610ed157600080fd5b505af1158015610ee5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f099190611b27565b905080610f285760405162461bcd60e51b815260040161023790611cd4565b6000610f33886116c7565b90506000610f40886116c7565b60015460405163fc57d4df60e01b81529192506000916110f191610fce9187916001600160a01b03169063fc57d4df90610f7e908f90600401611b60565b60206040518083038186803b158015610f9657600080fd5b505afa158015610faa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106559190611b27565b6102db600160009054906101000a90046001600160a01b03166001600160a01b031663fc57d4df8e6040518263ffffffff1660e01b81526004016110129190611b60565b60206040518083038186803b15801561102a57600080fd5b505afa15801561103e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110629190611b27565b6002546040805163231d97a560e21b81529051610655926001600160a01b031691638c765e94916004808301926020929190829003018186803b1580156110a857600080fd5b505afa1580156110bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e09190611b27565b6106558e60128b9003600a0a611652565b9050611104816012849003600a0a611695565b9a60009a508b9950975050505050505050565b6002546040516305189e1160e41b81526000918291829182916060916001600160a01b031690635189e11090611151908c90600401611b60565b60006040518083038186803b15801561116957600080fd5b505afa15801561117d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111a5919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906111db908590600401611b8e565b60006040518083038186803b1580156111f357600080fd5b505afa158015611207573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261122f91908101906119f8565b905060005b825181101561160f57600061124e84838151811061045b57fe5b905082828151811061125c57fe5b6020026020010151600014156112845760405162461bcd60e51b815260040161023790611be6565b61128c61187c565b84838151811061129857fe5b60200260200101516001600160a01b03166392fa4e8e8e6040518263ffffffff1660e01b81526004016112cb9190611b60565b606060405180830381600087803b1580156112e557600080fd5b505af11580156112f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131d9190611a98565b60035486519192506000916001600160a01b039091169087908690811061134057fe5b60200260200101516001600160a01b0316148015611378575067010a741a4627800085858151811061136e57fe5b6020026020010151115b1561144d576002548651611446916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a9081106113b757fe5b60200260200101516040518263ffffffff1660e01b81526004016113db9190611b60565b60806040518083038186803b1580156113f357600080fd5b505afa158015611407573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142b9190611ad5565b6060015160408601516106559067010a741a46278000611652565b905061152c565b6002548651611529916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a90811061148757fe5b60200260200101516040518263ffffffff1660e01b81526004016114ab9190611b60565b60806040518083038186803b1580156114c357600080fd5b505afa1580156114d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114fb9190611ad5565b6060015161065589898151811061150e57fe5b6020026020010151876040015161165290919063ffffffff16565b90505b61156361155c670de0b6b3a76400006102db8461065588601203600a0a886000015161165290919063ffffffff16565b89906117d1565b97506115af6115a8670de0b6b3a76400006102db88888151811061158357fe5b602002602001015161065588601203600a0a886020015161165290919063ffffffff16565b88906117d1565b96508c6001600160a01b03168685815181106115c757fe5b60200260200101516001600160a01b03161415611604576116016115a88d8d848989815181106115f357fe5b6020026020010151886117fa565b96505b505050600101611234565b5082841161161e576000611628565b6116288484611854565b95508284116116405761163b8385611854565b611643565b60005b94505050505094509492505050565b6000826116615750600061168f565b8282028284828161166e57fe5b041461168c5760405162461bcd60e51b815260040161023790611efd565b90505b92915050565b60008082116116b65760405162461bcd60e51b815260040161023790611ddf565b8183816116bf57fe5b049392505050565b600080826001600160a01b0316636f307dc36040518163ffffffff1660e01b815260040160206040518083038186803b15801561170357600080fd5b505afa158015611717573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173b91906118b9565b90506001600160a01b03811661175457601291506117cb565b806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561178d57600080fd5b505afa1580156117a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117c59190611b3f565b60ff1691505b50919050565b60008282018381101561168c5760405162461bcd60e51b815260040161023790611c9d565b3390565b600061181d670de0b6b3a76400006102db866106558a6012889003600a0a611652565b905061184a611843670de0b6b3a76400006102db866106558a6012899003600a0a611652565b82906117d1565b9695505050505050565b6000828211156118765760405162461bcd60e51b815260040161023790611da8565b50900390565b60405180606001604052806000815260200160008152602001600081525090565b6000602082840312156118ae578081fd5b813561168c8161204b565b6000602082840312156118ca578081fd5b815161168c8161204b565b6000806000606084860312156118e9578182fd5b83356118f48161204b565b925060208401356119048161204b565b929592945050506040919091013590565b6000806000806080858703121561192a578081fd5b84356119358161204b565b935060208501356119458161204b565b93969395505050506040820135916060013590565b6000602080838503121561196c578182fd5b825167ffffffffffffffff811115611982578283fd5b8301601f81018513611992578283fd5b80516119a56119a08261202b565b612004565b81815283810190838501858402850186018910156119c1578687fd5b8694505b838510156119ec5780516119d88161204b565b8352600194909401939185019185016119c5565b50979650505050505050565b60006020808385031215611a0a578182fd5b825167ffffffffffffffff811115611a20578283fd5b8301601f81018513611a30578283fd5b8051611a3e6119a08261202b565b8181528381019083850185840285018601891015611a5a578687fd5b8694505b838510156119ec578051835260019490940193918501918501611a5e565b600060208284031215611a8d578081fd5b815161168c81612063565b600060608284031215611aa9578081fd5b611ab36060612004565b8251815260208301516020820152604083015160408201528091505092915050565b600060808284031215611ae6578081fd5b611af06080612004565b8251611afb81612063565b808252506020830151602082015260408301516040820152606083015160608201528091505092915050565b600060208284031215611b38578081fd5b5051919050565b600060208284031215611b50578081fd5b815160ff8116811461168c578182fd5b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015611bcf5783516001600160a01b031683529284019291840191600101611baa565b50909695505050505050565b901515815260200190565b6020808252601690820152752b30b634b230ba37b91d10383934b1b29032b93937b960511b604082015260600190565b60208082526021908201527f56616c696461746f723a20556e6465726c79696e67207072696365206572726f6040820152603960f91b606082015260800190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526033908201527f56616c696461746f723a2065786368616e676552617465206f66206c546f6b656040820152726e436f6c6c61746572616c206973207a65726f60681b606082015260800190565b6020808252601f908201527f56616c696461746f723a20656e7465724d61726b657420726571756972656400604082015260600190565b6020808252602a908201527f56616c696461746f723a20696e76616c696420707269636543616c63756c61746040820152696f72206164647265737360b01b606082015260800190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b602080825260139082015272185b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604082015260600190565b60208082526022908201527f56616c696461746f723a20746f6f20736d616c6c20626f72726f7720616d6f756040820152611b9d60f21b606082015260800190565b60208082526021908201527f56616c696461746f723a20496e73756666696369656e742073686f727466616c6040820152601b60fa1b606082015260800190565b6020808252601b908201527f56616c696461746f723a20636f726520616c7265616479207365740000000000604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526024908201527f56616c696461746f723a206d61726b657420626f72726f77206361702072656160408201526318da195960e21b606082015260800190565b6020808252601f908201527f56616c696461746f723a20696e76616c696420636f7265206164647265737300604082015260600190565b9283526020830191909152604082015260600190565b60405181810167ffffffffffffffff8111828210171561202357600080fd5b604052919050565b600067ffffffffffffffff821115612041578081fd5b5060209081020190565b6001600160a01b038116811461206057600080fd5b50565b801515811461206057600080fdfea26469706673582212203a341cb9fdf943e35fd9d0c8e789e0d69ee7c11ae267c21da988555add48c88064736f6c634300060c0033

Block Transaction Gas Used Reward
view all blocks sequenced

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

Validator Index Block Amount
View All Withdrawals

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

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