Source Code
Overview
ETH Balance
ETH Value
$0.00View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Cross-Chain Transactions
Loading...
Loading
Contract Name:
Validator
Compiler Version
v0.6.12+commit.27d51765
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// 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();
}
}// 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;
}
}// 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;
}
}// 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;
}// 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);
}
}// 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;
}// 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);
}
}
}// 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);
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
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"}]Contract Creation Code
608060405234801561001057600080fd5b50600061001b61006a565b600080546001600160a01b0319166001600160a01b0383169081178255604051929350917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a35061006e565b3390565b6120a78061007d6000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638da5cb5b1161008c578063eabe7d9111610066578063eabe7d91146101b5578063f2f4eb26146101c8578063f2fde38b146101d0578063f643b5df146101e3576100ea565b80638da5cb5b14610187578063c4d66de81461018f578063da3d454c146101a2576100ea565b80636922d7b6116100c85780636922d7b614610142578063715018a6146101575780637dc0d1d01461015f5780638000963014610174576100ea565b8063158ef93e146100ef57806325d024621461010d5780635ec88c7914610120575b600080fd5b6100f76101f6565b6040516101049190611bdb565b60405180910390f35b6100f761011b366004611915565b610206565b61013361012e36600461189d565b6102f0565b60405161010493929190611fee565b61015561015036600461189d565b610758565b005b6101556107df565b610167610868565b6040516101049190611b60565b61015561018236600461189d565b610877565b610167610927565b61015561019d36600461189d565b610936565b6100f76101b03660046118d5565b6109cf565b6100f76101c33660046118d5565b610c7d565b610167610c98565b6101556101de36600461189d565b610ca7565b6101336101f13660046118d5565b610d67565b600354600160a01b900460ff1681565b600080610217856000806000611117565b915050806102405760405162461bcd60e51b815260040161023790611e85565b60405180910390fd5b6040516341cce05d60e11b81526000906001600160a01b03881690638399c0ba9061026f908990600401611b60565b602060405180830381600087803b15801561028957600080fd5b505af115801561029d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c19190611b27565b905060006102e1670de0b6b3a76400006102db8785611652565b90611695565b90951115979650505050505050565b6002546040516305189e1160e41b8152600091829182916060916001600160a01b0390911690635189e1109061032a908890600401611b60565b60006040518083038186803b15801561034257600080fd5b505afa158015610356573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261037e919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906103b4908590600401611b8e565b60006040518083038186803b1580156103cc57600080fd5b505afa1580156103e0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261040891908101906119f8565b905060005b825181101561074e5781818151811061042257fe5b60200260200101516000141561044a5760405162461bcd60e51b815260040161023790611be6565b600061046884838151811061045b57fe5b60200260200101516116c7565b905061047261187c565b84838151811061047e57fe5b60200260200101516001600160a01b031663014a296f8a6040518263ffffffff1660e01b81526004016104b19190611b60565b60606040518083038186803b1580156104c957600080fd5b505afa1580156104dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105019190611a98565b60035486519192506000916001600160a01b039091169087908690811061052457fe5b60200260200101516001600160a01b031614801561055c575067010a741a4627800085858151811061055257fe5b6020026020010151115b15610570575067010a741a46278000610587565b84848151811061057c57fe5b602002602001015190505b60025486516000916001600160a01b031690636e8584fd908990889081106105ab57fe5b60200260200101516040518263ffffffff1660e01b81526004016105cf9190611b60565b60806040518083038186803b1580156105e757600080fd5b505afa1580156105fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061f9190611ad5565b606001519050600061065b6ec097ce7bc90715b34b9f10000000006102db8461065587896040015161165290919063ffffffff16565b90611652565b905061069461068d670de0b6b3a76400006102db846106558a601203600a0a8a6000015161165290919063ffffffff16565b8c906117d1565b9a506106ef6106e86ec097ce7bc90715b34b9f10000000006102db8a8a815181106106bb57fe5b60200260200101516106558a601203600a0a6106558b604001518c6000015161165290919063ffffffff16565b8b906117d1565b995061073b610734670de0b6b3a76400006102db8a8a8151811061070f57fe5b60200260200101516106558a601203600a0a8a6020015161165290919063ffffffff16565b8a906117d1565b9850506001909401935061040d92505050565b5050509193909250565b6107606117f6565b6001600160a01b0316610771610927565b6001600160a01b0316146107975760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166107bd5760405162461bcd60e51b815260040161023790611d5e565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6107e76117f6565b6001600160a01b03166107f8610927565b6001600160a01b03161461081e5760405162461bcd60e51b815260040161023790611f3e565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6001546001600160a01b031681565b61087f6117f6565b6001600160a01b0316610890610927565b6001600160a01b0316146108b65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166108dc5760405162461bcd60e51b815260040161023790611fb7565b6002546001600160a01b0316156109055760405162461bcd60e51b815260040161023790611ec6565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031690565b61093e6117f6565b6001600160a01b031661094f610927565b6001600160a01b0316146109755760405162461bcd60e51b815260040161023790611f3e565b600354600160a01b900460ff161561099f5760405162461bcd60e51b815260040161023790611e16565b6003805460ff60a01b196001600160a01b039093166001600160a01b03199091161791909116600160a01b179055565b60006103e882116109f25760405162461bcd60e51b815260040161023790611e43565b60025460405163929fe9a160e01b81526001600160a01b039091169063929fe9a190610a249086908890600401611b74565b60206040518083038186803b158015610a3c57600080fd5b505afa158015610a50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a749190611a7c565b610a905760405162461bcd60e51b815260040161023790611d27565b60015460405163fc57d4df60e01b81526000916001600160a01b03169063fc57d4df90610ac1908890600401611b60565b60206040518083038186803b158015610ad957600080fd5b505afa158015610aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b119190611b27565b11610b2e5760405162461bcd60e51b815260040161023790611c16565b600254604051636e8584fd60e01b81526000916001600160a01b031690636e8584fd90610b5f908890600401611b60565b60806040518083038186803b158015610b7757600080fd5b505afa158015610b8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610baf9190611ad5565b6040015190508015610c62576000856001600160a01b031663ecdaff7e6040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610bf857600080fd5b505af1158015610c0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c309190611b27565b90506000610c3e82866117d1565b9050828110610c5f5760405162461bcd60e51b815260040161023790611f73565b50505b6000610c718587600087611117565b15979650505050505050565b600080610c8d8486856000611117565b159695505050505050565b6002546001600160a01b031681565b610caf6117f6565b6001600160a01b0316610cc0610927565b6001600160a01b031614610ce65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b038116610d0c5760405162461bcd60e51b815260040161023790611c57565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60015460405163fc57d4df60e01b8152600091829182916001600160a01b03169063fc57d4df90610d9c908990600401611b60565b60206040518083038186803b158015610db457600080fd5b505afa158015610dc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dec9190611b27565b15801590610e78575060015460405163fc57d4df60e01b81526001600160a01b039091169063fc57d4df90610e25908890600401611b60565b60206040518083038186803b158015610e3d57600080fd5b505afa158015610e51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e759190611b27565b15155b610e945760405162461bcd60e51b815260040161023790611be6565b6000856001600160a01b0316638b9db0376040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610ed157600080fd5b505af1158015610ee5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f099190611b27565b905080610f285760405162461bcd60e51b815260040161023790611cd4565b6000610f33886116c7565b90506000610f40886116c7565b60015460405163fc57d4df60e01b81529192506000916110f191610fce9187916001600160a01b03169063fc57d4df90610f7e908f90600401611b60565b60206040518083038186803b158015610f9657600080fd5b505afa158015610faa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106559190611b27565b6102db600160009054906101000a90046001600160a01b03166001600160a01b031663fc57d4df8e6040518263ffffffff1660e01b81526004016110129190611b60565b60206040518083038186803b15801561102a57600080fd5b505afa15801561103e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110629190611b27565b6002546040805163231d97a560e21b81529051610655926001600160a01b031691638c765e94916004808301926020929190829003018186803b1580156110a857600080fd5b505afa1580156110bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e09190611b27565b6106558e60128b9003600a0a611652565b9050611104816012849003600a0a611695565b9a60009a508b9950975050505050505050565b6002546040516305189e1160e41b81526000918291829182916060916001600160a01b031690635189e11090611151908c90600401611b60565b60006040518083038186803b15801561116957600080fd5b505afa15801561117d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111a5919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906111db908590600401611b8e565b60006040518083038186803b1580156111f357600080fd5b505afa158015611207573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261122f91908101906119f8565b905060005b825181101561160f57600061124e84838151811061045b57fe5b905082828151811061125c57fe5b6020026020010151600014156112845760405162461bcd60e51b815260040161023790611be6565b61128c61187c565b84838151811061129857fe5b60200260200101516001600160a01b03166392fa4e8e8e6040518263ffffffff1660e01b81526004016112cb9190611b60565b606060405180830381600087803b1580156112e557600080fd5b505af11580156112f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131d9190611a98565b60035486519192506000916001600160a01b039091169087908690811061134057fe5b60200260200101516001600160a01b0316148015611378575067010a741a4627800085858151811061136e57fe5b6020026020010151115b1561144d576002548651611446916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a9081106113b757fe5b60200260200101516040518263ffffffff1660e01b81526004016113db9190611b60565b60806040518083038186803b1580156113f357600080fd5b505afa158015611407573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142b9190611ad5565b6060015160408601516106559067010a741a46278000611652565b905061152c565b6002548651611529916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a90811061148757fe5b60200260200101516040518263ffffffff1660e01b81526004016114ab9190611b60565b60806040518083038186803b1580156114c357600080fd5b505afa1580156114d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114fb9190611ad5565b6060015161065589898151811061150e57fe5b6020026020010151876040015161165290919063ffffffff16565b90505b61156361155c670de0b6b3a76400006102db8461065588601203600a0a886000015161165290919063ffffffff16565b89906117d1565b97506115af6115a8670de0b6b3a76400006102db88888151811061158357fe5b602002602001015161065588601203600a0a886020015161165290919063ffffffff16565b88906117d1565b96508c6001600160a01b03168685815181106115c757fe5b60200260200101516001600160a01b03161415611604576116016115a88d8d848989815181106115f357fe5b6020026020010151886117fa565b96505b505050600101611234565b5082841161161e576000611628565b6116288484611854565b95508284116116405761163b8385611854565b611643565b60005b94505050505094509492505050565b6000826116615750600061168f565b8282028284828161166e57fe5b041461168c5760405162461bcd60e51b815260040161023790611efd565b90505b92915050565b60008082116116b65760405162461bcd60e51b815260040161023790611ddf565b8183816116bf57fe5b049392505050565b600080826001600160a01b0316636f307dc36040518163ffffffff1660e01b815260040160206040518083038186803b15801561170357600080fd5b505afa158015611717573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173b91906118b9565b90506001600160a01b03811661175457601291506117cb565b806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561178d57600080fd5b505afa1580156117a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117c59190611b3f565b60ff1691505b50919050565b60008282018381101561168c5760405162461bcd60e51b815260040161023790611c9d565b3390565b600061181d670de0b6b3a76400006102db866106558a6012889003600a0a611652565b905061184a611843670de0b6b3a76400006102db866106558a6012899003600a0a611652565b82906117d1565b9695505050505050565b6000828211156118765760405162461bcd60e51b815260040161023790611da8565b50900390565b60405180606001604052806000815260200160008152602001600081525090565b6000602082840312156118ae578081fd5b813561168c8161204b565b6000602082840312156118ca578081fd5b815161168c8161204b565b6000806000606084860312156118e9578182fd5b83356118f48161204b565b925060208401356119048161204b565b929592945050506040919091013590565b6000806000806080858703121561192a578081fd5b84356119358161204b565b935060208501356119458161204b565b93969395505050506040820135916060013590565b6000602080838503121561196c578182fd5b825167ffffffffffffffff811115611982578283fd5b8301601f81018513611992578283fd5b80516119a56119a08261202b565b612004565b81815283810190838501858402850186018910156119c1578687fd5b8694505b838510156119ec5780516119d88161204b565b8352600194909401939185019185016119c5565b50979650505050505050565b60006020808385031215611a0a578182fd5b825167ffffffffffffffff811115611a20578283fd5b8301601f81018513611a30578283fd5b8051611a3e6119a08261202b565b8181528381019083850185840285018601891015611a5a578687fd5b8694505b838510156119ec578051835260019490940193918501918501611a5e565b600060208284031215611a8d578081fd5b815161168c81612063565b600060608284031215611aa9578081fd5b611ab36060612004565b8251815260208301516020820152604083015160408201528091505092915050565b600060808284031215611ae6578081fd5b611af06080612004565b8251611afb81612063565b808252506020830151602082015260408301516040820152606083015160608201528091505092915050565b600060208284031215611b38578081fd5b5051919050565b600060208284031215611b50578081fd5b815160ff8116811461168c578182fd5b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015611bcf5783516001600160a01b031683529284019291840191600101611baa565b50909695505050505050565b901515815260200190565b6020808252601690820152752b30b634b230ba37b91d10383934b1b29032b93937b960511b604082015260600190565b60208082526021908201527f56616c696461746f723a20556e6465726c79696e67207072696365206572726f6040820152603960f91b606082015260800190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526033908201527f56616c696461746f723a2065786368616e676552617465206f66206c546f6b656040820152726e436f6c6c61746572616c206973207a65726f60681b606082015260800190565b6020808252601f908201527f56616c696461746f723a20656e7465724d61726b657420726571756972656400604082015260600190565b6020808252602a908201527f56616c696461746f723a20696e76616c696420707269636543616c63756c61746040820152696f72206164647265737360b01b606082015260800190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b602080825260139082015272185b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604082015260600190565b60208082526022908201527f56616c696461746f723a20746f6f20736d616c6c20626f72726f7720616d6f756040820152611b9d60f21b606082015260800190565b60208082526021908201527f56616c696461746f723a20496e73756666696369656e742073686f727466616c6040820152601b60fa1b606082015260800190565b6020808252601b908201527f56616c696461746f723a20636f726520616c7265616479207365740000000000604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526024908201527f56616c696461746f723a206d61726b657420626f72726f77206361702072656160408201526318da195960e21b606082015260800190565b6020808252601f908201527f56616c696461746f723a20696e76616c696420636f7265206164647265737300604082015260600190565b9283526020830191909152604082015260600190565b60405181810167ffffffffffffffff8111828210171561202357600080fd5b604052919050565b600067ffffffffffffffff821115612041578081fd5b5060209081020190565b6001600160a01b038116811461206057600080fd5b50565b801515811461206057600080fdfea26469706673582212203a341cb9fdf943e35fd9d0c8e789e0d69ee7c11ae267c21da988555add48c88064736f6c634300060c0033
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638da5cb5b1161008c578063eabe7d9111610066578063eabe7d91146101b5578063f2f4eb26146101c8578063f2fde38b146101d0578063f643b5df146101e3576100ea565b80638da5cb5b14610187578063c4d66de81461018f578063da3d454c146101a2576100ea565b80636922d7b6116100c85780636922d7b614610142578063715018a6146101575780637dc0d1d01461015f5780638000963014610174576100ea565b8063158ef93e146100ef57806325d024621461010d5780635ec88c7914610120575b600080fd5b6100f76101f6565b6040516101049190611bdb565b60405180910390f35b6100f761011b366004611915565b610206565b61013361012e36600461189d565b6102f0565b60405161010493929190611fee565b61015561015036600461189d565b610758565b005b6101556107df565b610167610868565b6040516101049190611b60565b61015561018236600461189d565b610877565b610167610927565b61015561019d36600461189d565b610936565b6100f76101b03660046118d5565b6109cf565b6100f76101c33660046118d5565b610c7d565b610167610c98565b6101556101de36600461189d565b610ca7565b6101336101f13660046118d5565b610d67565b600354600160a01b900460ff1681565b600080610217856000806000611117565b915050806102405760405162461bcd60e51b815260040161023790611e85565b60405180910390fd5b6040516341cce05d60e11b81526000906001600160a01b03881690638399c0ba9061026f908990600401611b60565b602060405180830381600087803b15801561028957600080fd5b505af115801561029d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c19190611b27565b905060006102e1670de0b6b3a76400006102db8785611652565b90611695565b90951115979650505050505050565b6002546040516305189e1160e41b8152600091829182916060916001600160a01b0390911690635189e1109061032a908890600401611b60565b60006040518083038186803b15801561034257600080fd5b505afa158015610356573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261037e919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906103b4908590600401611b8e565b60006040518083038186803b1580156103cc57600080fd5b505afa1580156103e0573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261040891908101906119f8565b905060005b825181101561074e5781818151811061042257fe5b60200260200101516000141561044a5760405162461bcd60e51b815260040161023790611be6565b600061046884838151811061045b57fe5b60200260200101516116c7565b905061047261187c565b84838151811061047e57fe5b60200260200101516001600160a01b031663014a296f8a6040518263ffffffff1660e01b81526004016104b19190611b60565b60606040518083038186803b1580156104c957600080fd5b505afa1580156104dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105019190611a98565b60035486519192506000916001600160a01b039091169087908690811061052457fe5b60200260200101516001600160a01b031614801561055c575067010a741a4627800085858151811061055257fe5b6020026020010151115b15610570575067010a741a46278000610587565b84848151811061057c57fe5b602002602001015190505b60025486516000916001600160a01b031690636e8584fd908990889081106105ab57fe5b60200260200101516040518263ffffffff1660e01b81526004016105cf9190611b60565b60806040518083038186803b1580156105e757600080fd5b505afa1580156105fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061f9190611ad5565b606001519050600061065b6ec097ce7bc90715b34b9f10000000006102db8461065587896040015161165290919063ffffffff16565b90611652565b905061069461068d670de0b6b3a76400006102db846106558a601203600a0a8a6000015161165290919063ffffffff16565b8c906117d1565b9a506106ef6106e86ec097ce7bc90715b34b9f10000000006102db8a8a815181106106bb57fe5b60200260200101516106558a601203600a0a6106558b604001518c6000015161165290919063ffffffff16565b8b906117d1565b995061073b610734670de0b6b3a76400006102db8a8a8151811061070f57fe5b60200260200101516106558a601203600a0a8a6020015161165290919063ffffffff16565b8a906117d1565b9850506001909401935061040d92505050565b5050509193909250565b6107606117f6565b6001600160a01b0316610771610927565b6001600160a01b0316146107975760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166107bd5760405162461bcd60e51b815260040161023790611d5e565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6107e76117f6565b6001600160a01b03166107f8610927565b6001600160a01b03161461081e5760405162461bcd60e51b815260040161023790611f3e565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6001546001600160a01b031681565b61087f6117f6565b6001600160a01b0316610890610927565b6001600160a01b0316146108b65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b0381166108dc5760405162461bcd60e51b815260040161023790611fb7565b6002546001600160a01b0316156109055760405162461bcd60e51b815260040161023790611ec6565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031690565b61093e6117f6565b6001600160a01b031661094f610927565b6001600160a01b0316146109755760405162461bcd60e51b815260040161023790611f3e565b600354600160a01b900460ff161561099f5760405162461bcd60e51b815260040161023790611e16565b6003805460ff60a01b196001600160a01b039093166001600160a01b03199091161791909116600160a01b179055565b60006103e882116109f25760405162461bcd60e51b815260040161023790611e43565b60025460405163929fe9a160e01b81526001600160a01b039091169063929fe9a190610a249086908890600401611b74565b60206040518083038186803b158015610a3c57600080fd5b505afa158015610a50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a749190611a7c565b610a905760405162461bcd60e51b815260040161023790611d27565b60015460405163fc57d4df60e01b81526000916001600160a01b03169063fc57d4df90610ac1908890600401611b60565b60206040518083038186803b158015610ad957600080fd5b505afa158015610aed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b119190611b27565b11610b2e5760405162461bcd60e51b815260040161023790611c16565b600254604051636e8584fd60e01b81526000916001600160a01b031690636e8584fd90610b5f908890600401611b60565b60806040518083038186803b158015610b7757600080fd5b505afa158015610b8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610baf9190611ad5565b6040015190508015610c62576000856001600160a01b031663ecdaff7e6040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610bf857600080fd5b505af1158015610c0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c309190611b27565b90506000610c3e82866117d1565b9050828110610c5f5760405162461bcd60e51b815260040161023790611f73565b50505b6000610c718587600087611117565b15979650505050505050565b600080610c8d8486856000611117565b159695505050505050565b6002546001600160a01b031681565b610caf6117f6565b6001600160a01b0316610cc0610927565b6001600160a01b031614610ce65760405162461bcd60e51b815260040161023790611f3e565b6001600160a01b038116610d0c5760405162461bcd60e51b815260040161023790611c57565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b60015460405163fc57d4df60e01b8152600091829182916001600160a01b03169063fc57d4df90610d9c908990600401611b60565b60206040518083038186803b158015610db457600080fd5b505afa158015610dc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dec9190611b27565b15801590610e78575060015460405163fc57d4df60e01b81526001600160a01b039091169063fc57d4df90610e25908890600401611b60565b60206040518083038186803b158015610e3d57600080fd5b505afa158015610e51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e759190611b27565b15155b610e945760405162461bcd60e51b815260040161023790611be6565b6000856001600160a01b0316638b9db0376040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610ed157600080fd5b505af1158015610ee5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f099190611b27565b905080610f285760405162461bcd60e51b815260040161023790611cd4565b6000610f33886116c7565b90506000610f40886116c7565b60015460405163fc57d4df60e01b81529192506000916110f191610fce9187916001600160a01b03169063fc57d4df90610f7e908f90600401611b60565b60206040518083038186803b158015610f9657600080fd5b505afa158015610faa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106559190611b27565b6102db600160009054906101000a90046001600160a01b03166001600160a01b031663fc57d4df8e6040518263ffffffff1660e01b81526004016110129190611b60565b60206040518083038186803b15801561102a57600080fd5b505afa15801561103e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110629190611b27565b6002546040805163231d97a560e21b81529051610655926001600160a01b031691638c765e94916004808301926020929190829003018186803b1580156110a857600080fd5b505afa1580156110bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e09190611b27565b6106558e60128b9003600a0a611652565b9050611104816012849003600a0a611695565b9a60009a508b9950975050505050505050565b6002546040516305189e1160e41b81526000918291829182916060916001600160a01b031690635189e11090611151908c90600401611b60565b60006040518083038186803b15801561116957600080fd5b505afa15801561117d573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111a5919081019061195a565b6001546040516348a1371b60e01b81529192506060916001600160a01b03909116906348a1371b906111db908590600401611b8e565b60006040518083038186803b1580156111f357600080fd5b505afa158015611207573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261122f91908101906119f8565b905060005b825181101561160f57600061124e84838151811061045b57fe5b905082828151811061125c57fe5b6020026020010151600014156112845760405162461bcd60e51b815260040161023790611be6565b61128c61187c565b84838151811061129857fe5b60200260200101516001600160a01b03166392fa4e8e8e6040518263ffffffff1660e01b81526004016112cb9190611b60565b606060405180830381600087803b1580156112e557600080fd5b505af11580156112f9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131d9190611a98565b60035486519192506000916001600160a01b039091169087908690811061134057fe5b60200260200101516001600160a01b0316148015611378575067010a741a4627800085858151811061136e57fe5b6020026020010151115b1561144d576002548651611446916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a9081106113b757fe5b60200260200101516040518263ffffffff1660e01b81526004016113db9190611b60565b60806040518083038186803b1580156113f357600080fd5b505afa158015611407573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142b9190611ad5565b6060015160408601516106559067010a741a46278000611652565b905061152c565b6002548651611529916ec097ce7bc90715b34b9f1000000000916102db916001600160a01b031690636e8584fd908b908a90811061148757fe5b60200260200101516040518263ffffffff1660e01b81526004016114ab9190611b60565b60806040518083038186803b1580156114c357600080fd5b505afa1580156114d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114fb9190611ad5565b6060015161065589898151811061150e57fe5b6020026020010151876040015161165290919063ffffffff16565b90505b61156361155c670de0b6b3a76400006102db8461065588601203600a0a886000015161165290919063ffffffff16565b89906117d1565b97506115af6115a8670de0b6b3a76400006102db88888151811061158357fe5b602002602001015161065588601203600a0a886020015161165290919063ffffffff16565b88906117d1565b96508c6001600160a01b03168685815181106115c757fe5b60200260200101516001600160a01b03161415611604576116016115a88d8d848989815181106115f357fe5b6020026020010151886117fa565b96505b505050600101611234565b5082841161161e576000611628565b6116288484611854565b95508284116116405761163b8385611854565b611643565b60005b94505050505094509492505050565b6000826116615750600061168f565b8282028284828161166e57fe5b041461168c5760405162461bcd60e51b815260040161023790611efd565b90505b92915050565b60008082116116b65760405162461bcd60e51b815260040161023790611ddf565b8183816116bf57fe5b049392505050565b600080826001600160a01b0316636f307dc36040518163ffffffff1660e01b815260040160206040518083038186803b15801561170357600080fd5b505afa158015611717573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173b91906118b9565b90506001600160a01b03811661175457601291506117cb565b806001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561178d57600080fd5b505afa1580156117a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117c59190611b3f565b60ff1691505b50919050565b60008282018381101561168c5760405162461bcd60e51b815260040161023790611c9d565b3390565b600061181d670de0b6b3a76400006102db866106558a6012889003600a0a611652565b905061184a611843670de0b6b3a76400006102db866106558a6012899003600a0a611652565b82906117d1565b9695505050505050565b6000828211156118765760405162461bcd60e51b815260040161023790611da8565b50900390565b60405180606001604052806000815260200160008152602001600081525090565b6000602082840312156118ae578081fd5b813561168c8161204b565b6000602082840312156118ca578081fd5b815161168c8161204b565b6000806000606084860312156118e9578182fd5b83356118f48161204b565b925060208401356119048161204b565b929592945050506040919091013590565b6000806000806080858703121561192a578081fd5b84356119358161204b565b935060208501356119458161204b565b93969395505050506040820135916060013590565b6000602080838503121561196c578182fd5b825167ffffffffffffffff811115611982578283fd5b8301601f81018513611992578283fd5b80516119a56119a08261202b565b612004565b81815283810190838501858402850186018910156119c1578687fd5b8694505b838510156119ec5780516119d88161204b565b8352600194909401939185019185016119c5565b50979650505050505050565b60006020808385031215611a0a578182fd5b825167ffffffffffffffff811115611a20578283fd5b8301601f81018513611a30578283fd5b8051611a3e6119a08261202b565b8181528381019083850185840285018601891015611a5a578687fd5b8694505b838510156119ec578051835260019490940193918501918501611a5e565b600060208284031215611a8d578081fd5b815161168c81612063565b600060608284031215611aa9578081fd5b611ab36060612004565b8251815260208301516020820152604083015160408201528091505092915050565b600060808284031215611ae6578081fd5b611af06080612004565b8251611afb81612063565b808252506020830151602082015260408301516040820152606083015160608201528091505092915050565b600060208284031215611b38578081fd5b5051919050565b600060208284031215611b50578081fd5b815160ff8116811461168c578182fd5b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015611bcf5783516001600160a01b031683529284019291840191600101611baa565b50909695505050505050565b901515815260200190565b6020808252601690820152752b30b634b230ba37b91d10383934b1b29032b93937b960511b604082015260600190565b60208082526021908201527f56616c696461746f723a20556e6465726c79696e67207072696365206572726f6040820152603960f91b606082015260800190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526033908201527f56616c696461746f723a2065786368616e676552617465206f66206c546f6b656040820152726e436f6c6c61746572616c206973207a65726f60681b606082015260800190565b6020808252601f908201527f56616c696461746f723a20656e7465724d61726b657420726571756972656400604082015260600190565b6020808252602a908201527f56616c696461746f723a20696e76616c696420707269636543616c63756c61746040820152696f72206164647265737360b01b606082015260800190565b6020808252601e908201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604082015260600190565b6020808252601a908201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604082015260600190565b602080825260139082015272185b1c9958591e481a5b9a5d1a585b1a5e9959606a1b604082015260600190565b60208082526022908201527f56616c696461746f723a20746f6f20736d616c6c20626f72726f7720616d6f756040820152611b9d60f21b606082015260800190565b60208082526021908201527f56616c696461746f723a20496e73756666696369656e742073686f727466616c6040820152601b60fa1b606082015260800190565b6020808252601b908201527f56616c696461746f723a20636f726520616c7265616479207365740000000000604082015260600190565b60208082526021908201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6040820152607760f81b606082015260800190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526024908201527f56616c696461746f723a206d61726b657420626f72726f77206361702072656160408201526318da195960e21b606082015260800190565b6020808252601f908201527f56616c696461746f723a20696e76616c696420636f7265206164647265737300604082015260600190565b9283526020830191909152604082015260600190565b60405181810167ffffffffffffffff8111828210171561202357600080fd5b604052919050565b600067ffffffffffffffff821115612041578081fd5b5060209081020190565b6001600160a01b038116811461206057600080fd5b50565b801515811461206057600080fdfea26469706673582212203a341cb9fdf943e35fd9d0c8e789e0d69ee7c11ae267c21da988555add48c88064736f6c634300060c0033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
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.