// SPDX-License-Identifier: MIT

/*
YYYYYYY       YYYYYYYFFFFFFFFFFFFFFFFFFFFFF   SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT         AAA               BBBBBBBBBBBBBBBBB   LLLLLLLLLLL             EEEEEEEEEEEEEEEEEEEEEE
Y:::::Y       Y:::::YF::::::::::::::::::::F SS:::::::::::::::ST:::::::::::::::::::::T        A:::A              B::::::::::::::::B  L:::::::::L             E::::::::::::::::::::E
Y:::::Y       Y:::::YF::::::::::::::::::::FS:::::SSSSSS::::::ST:::::::::::::::::::::T       A:::::A             B::::::BBBBBB:::::B L:::::::::L             E::::::::::::::::::::E
Y::::::Y     Y::::::YFF::::::FFFFFFFFF::::FS:::::S     SSSSSSST:::::TT:::::::TT:::::T      A:::::::A            BB:::::B     B:::::BLL:::::::LL             EE::::::EEEEEEEEE::::E
YYY:::::Y   Y:::::YYY  F:::::F       FFFFFFS:::::S            TTTTTT  T:::::T  TTTTTT     A:::::::::A             B::::B     B:::::B  L:::::L                 E:::::E       EEEEEE
   Y:::::Y Y:::::Y     F:::::F             S:::::S                    T:::::T            A:::::A:::::A            B::::B     B:::::B  L:::::L                 E:::::E             
    Y:::::Y:::::Y      F::::::FFFFFFFFFF    S::::SSSS                 T:::::T           A:::::A A:::::A           B::::BBBBBB:::::B   L:::::L                 E::::::EEEEEEEEEE   
     Y:::::::::Y       F:::::::::::::::F     SS::::::SSSSS            T:::::T          A:::::A   A:::::A          B:::::::::::::BB    L:::::L                 E:::::::::::::::E   
      Y:::::::Y        F:::::::::::::::F       SSS::::::::SS          T:::::T         A:::::A     A:::::A         B::::BBBBBB:::::B   L:::::L                 E:::::::::::::::E   
       Y:::::Y         F::::::FFFFFFFFFF          SSSSSS::::S         T:::::T        A:::::AAAAAAAAA:::::A        B::::B     B:::::B  L:::::L                 E::::::EEEEEEEEEE   
       Y:::::Y         F:::::F                         S:::::S        T:::::T       A:::::::::::::::::::::A       B::::B     B:::::B  L:::::L                 E:::::E             
       Y:::::Y         F:::::F                         S:::::S        T:::::T      A:::::AAAAAAAAAAAAA:::::A      B::::B     B:::::B  L:::::L         LLLLLL  E:::::E       EEEEEE
       Y:::::Y       FF:::::::FF           SSSSSSS     S:::::S      TT:::::::TT   A:::::A             A:::::A   BB:::::BBBBBB::::::BLL:::::::LLLLLLLLL:::::LEE::::::EEEEEEEE:::::E
    YYYY:::::YYYY    F::::::::FF           S::::::SSSSSS:::::S      T:::::::::T  A:::::A               A:::::A  B:::::::::::::::::B L::::::::::::::::::::::LE::::::::::::::::::::E
    Y:::::::::::Y    F::::::::FF           S:::::::::::::::SS       T:::::::::T A:::::A                 A:::::A B::::::::::::::::B  L::::::::::::::::::::::LE::::::::::::::::::::E
    YYYYYYYYYYYYY    FFFFFFFFFFF            SSSSSSSSSSSSSSS         TTTTTTTTTTTAAAAAAA                   AAAAAAABBBBBBBBBBBBBBBBB   LLLLLLLLLLLLLLLLLLLLLLLLEEEEEEEEEEEEEEEEEEEEEE
                                                                                                                                                                                  
*/

pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

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;
}
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);
}
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;
}
library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }
    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;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        return c;
    }
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}
library Address {
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly { size := extcodesize(account) }
        return size > 0;
    }
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{ value: amount }("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
      return functionCall(target, data, "Address: low-level call failed");
    }
    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }
    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }
    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        if (success) {
            return returndata;
        } else {
            if (returndata.length > 0) {
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}
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;
    }
}
abstract contract Ownable is Context {
    address private _owner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    constructor () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }
    function owner() public view returns (address) {
        return _owner;
    }
    modifier onlyOwner() {
        require(_owner == _msgSender(), "Ownable: caller is not the owner");
        _;
    }
    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

library Constants {
    uint256 private constant MAX = ~uint256(0);
    uint256 private constant _launchSupply = 60450 * 10**9;
    uint256 private constant _largeTotal = (MAX - (MAX % _launchSupply));

    uint256 private constant _baseExpansionFactor = 100;
    uint256 private constant _baseContractionFactor = 100;
    uint256 private constant _baseUtilityFee = 50;
    uint256 private constant _baseContractionCap = 1000;

    uint256 private constant _stabilizerFee = 250;
    uint256 private constant _stabilizationLowerBound = 50;
    uint256 private constant _stabilizationLowerReset = 75;
    uint256 private constant _stabilizationUpperBound = 150;
    uint256 private constant _stabilizationUpperReset = 125;
    uint256 private constant _stabilizePercent = 10;

    uint256 private constant _treasuryFee = 250;

    uint256 private constant _presaleMinIndividualCap = 1 ether;
    uint256 private constant _presaleMaxIndividualCap = 4 ether;
    uint256 private constant _presaleCap = 37200 * 10**9; 
    uint256 private constant _maxPresaleGas = 200000000000;

    uint256 private constant _epochLength = 4 hours;

    uint256 private constant _liquidityReward = 2 * 10**9;
    uint256 private constant _minForLiquidity = 10 * 10**9;
    uint256 private constant _minForCallerLiquidity = 10 * 10**9;

    address private constant _routerAddress = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address private constant _factoryAddress = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    address payable private constant _deployerAddress = 0xB4a43aEd87902A24cD66afBD3349Af812325Ca01;
    address private constant _treasuryAddress = 0xB4a43aEd87902A24cD66afBD3349Af812325Ca01;

    uint256 private constant _presaleRate = 31000;
    uint256 private constant _listingRate = 29063;

    string private constant _name = "YFStable";
    string private constant _symbol = "YFST";
    uint8 private constant _decimals = 9;

    /****** Getters *******/
    function getPresaleRate() internal pure returns (uint256) {
        return _presaleRate;
    }
     function getListingRate() internal pure returns (uint256) {
        return _listingRate;
    }
    function getLaunchSupply() internal pure returns (uint256) {
        return _launchSupply;
    }
    function getLargeTotal() internal pure returns (uint256) {
        return _largeTotal;
    }
    function getPresaleCap() internal pure returns (uint256) {
        return _presaleCap;
    }
    function getPresaleMinIndividualCap() internal pure returns (uint256) {
        return _presaleMinIndividualCap;
    }
    function getPresaleMaxIndividualCap() internal pure returns (uint256) {
        return _presaleMaxIndividualCap;
    }
    function getMaxPresaleGas() internal pure returns (uint256) {
        return _maxPresaleGas;
    }
    function getBaseExpansionFactor() internal pure returns (uint256) {
        return _baseExpansionFactor;
    }
    function getBaseContractionFactor() internal pure returns (uint256) {
        return _baseContractionFactor;
    }
    function getBaseContractionCap() internal pure returns (uint256) {
        return _baseContractionCap;
    }
    function getBaseUtilityFee() internal pure returns (uint256) {
        return _baseUtilityFee;
    }
    function getStabilizerFee() internal pure returns (uint256) {
        return _stabilizerFee;
    }
    function getStabilizationLowerBound() internal pure returns (uint256) {
        return _stabilizationLowerBound;
    }
    function getStabilizationLowerReset() internal pure returns (uint256) {
        return _stabilizationLowerReset;
    }
    function getStabilizationUpperBound() internal pure returns (uint256) {
        return _stabilizationUpperBound;
    }
    function getStabilizationUpperReset() internal pure returns (uint256) {
        return _stabilizationUpperReset;
    }
    function getStabilizePercent() internal pure returns (uint256) {
        return _stabilizePercent;
    }
    function getTreasuryFee() internal pure returns (uint256) {
        return _treasuryFee;
    }
    function getEpochLength() internal pure returns (uint256) {
        return _epochLength;
    }
    function getLiquidityReward() internal pure returns (uint256) {
        return _liquidityReward;
    }
    function getMinForLiquidity() internal pure returns (uint256) {
        return _minForLiquidity;
    }
    function getMinForCallerLiquidity() internal pure returns (uint256) {
        return _minForCallerLiquidity;
    }
    function getRouterAdd() internal pure returns (address) {
        return _routerAddress;
    }
    function getFactoryAdd() internal pure returns (address) {
        return _factoryAddress;
    }
    function getDeployerAdd() internal pure returns (address payable) {
        return _deployerAddress;
    }
    function getTreasuryAdd() internal pure returns (address) {
        return _treasuryAddress;
    }
    function getName() internal pure returns (string memory)  {
        return _name;
    }
    function getSymbol() internal pure returns (string memory) {
        return _symbol;
    }
    function getDecimals() internal pure returns (uint8) {
        return _decimals;
    }
}
contract State {

    mapping (address => uint256) _largeBalances;
    mapping (address => mapping (address => uint256)) _allowances;

    // Supported pools and data for measuring mint & burn factors
    struct PoolCounter {
        address pairToken;
        uint256 tokenBalance;
        uint256 pairTokenBalance;
        uint256 lpBalance;
        uint256 startTokenBalance;
        uint256 startPairTokenBalance;
    }
    address[] _supportedPools;
    mapping (address => PoolCounter) _poolCounters;
    mapping (address => bool) _isSupportedPool;
    address _mainPool;

    uint256 _currentEpoch;
    
    //Creating locked balances
    struct LockBox {
        address beneficiary;
        uint256 lockedBalance;
        uint256 unlockTime;
        bool locked;
    }
    LockBox[] _lockBoxes;
    mapping(address => uint256) _lockedBalance;
    mapping(address => bool) _hasLockedBalance;
    uint256 _totalLockedBalance;
 
    uint256 _largeTotal;
    uint256 _totalSupply;

    address _liquidityReserve;
    address _stabilizer;

    bool _presaleDone;
    address _presaleCon;
    
    bool _paused;
    
    bool _taxLess;
    mapping(address=>bool) _isTaxlessSetter;
}
contract Getters is State {
    using SafeMath for uint256;
    using Address for address;

    function getLargeBalances(address account) public view returns (uint256) {
        return _largeBalances[account];
    }
    function getAllowances(address account, address spender) public view returns (uint256) {
        return _allowances[account][spender];
    } 
    function getSupportedPools(uint256 index) public view returns (address) {
        return _supportedPools[index];
    }
    function getPoolCounters(address pool) public view returns (address, uint256, uint256, uint256, uint256, uint256) {
        PoolCounter memory pc = _poolCounters[pool];
        return (pc.pairToken, pc.tokenBalance, pc.pairTokenBalance, pc.lpBalance, pc.startTokenBalance, pc.startPairTokenBalance);
    }
    function isSupportedPool(address pool) public view returns (bool) {
        return _isSupportedPool[pool];
    }
    function mainPool() public view returns (address) {
        return _mainPool;
    }
    function getCurrentEpoch() public view returns (uint256) {
        return _currentEpoch;
    }
    function getLockBoxes(uint256 box) public view returns (address, uint256, uint256, bool) {
        LockBox memory lb = _lockBoxes[box];
        return (lb.beneficiary, lb.lockedBalance, lb.unlockTime, lb.locked);
    }
    function getLockedBalance(address account) public view returns (uint256) {
        return _lockedBalance[account];
    }
    function hasLockedBalance(address account) public view returns (bool) {
        return _hasLockedBalance[account];
    }
    function getTotalLockedBalance() public view returns (uint256) {
        return _totalLockedBalance;
    }
    function getLargeTotal() public view returns (uint256) {
        return _largeTotal;
    }
    function getTotalSupply() public view returns (uint256) {
        return _totalSupply;
    }
    function getLiquidityReserve() public view returns (address) {
        return _liquidityReserve;
    }
    function getStabilizer() public view returns (address) {
        return _stabilizer;
    }
    function isPresaleDone() public view returns (bool) {
        return _presaleDone;
    }
    function getPresaleAddress() public view returns (address) {
        return _presaleCon;
    }
    function isPaused() public view returns (bool) {
        return _paused;
    }
    function isTaxLess() public view returns (bool) {
        return _taxLess;
    }
    function isTaxlessSetter(address account) public view returns (bool) {
        return _isTaxlessSetter[account];
    }
    function getUniswapRouter() public view returns (IUniswapV2Router02) {
        return IUniswapV2Router02(Constants.getRouterAdd());
    }
    function getUniswapFactory() public view returns (IUniswapV2Factory) {
        return IUniswapV2Factory(Constants.getFactoryAdd());
    }
    function getFactor() public view returns(uint256) {
        if (_presaleDone) {
            return _largeTotal.div(_totalSupply);
        } else {
            return _largeTotal.div(Constants.getLaunchSupply());
        }
    }
    function getUpdatedPoolCounters(address pool, address pairToken) public view returns (uint256, uint256, uint256) {
        uint256 lpBalance = IERC20(pool).totalSupply();
        uint256 tokenBalance = IERC20(address(this)).balanceOf(pool);
        uint256 pairTokenBalance = IERC20(address(pairToken)).balanceOf(pool);
        return (tokenBalance, pairTokenBalance, lpBalance);
    }
    function getMintValue(address sender, uint256 amount) internal view returns(uint256, uint256, uint256) {
        uint256 expansionR = (_poolCounters[sender].pairTokenBalance).mul(_poolCounters[sender].startTokenBalance).mul(100).div(_poolCounters[sender].startPairTokenBalance).div(_poolCounters[sender].tokenBalance);
        uint256 mintAmount;
        if (expansionR > (Constants.getBaseExpansionFactor()).add(10000).div(100)) {
            uint256 mintFactor = expansionR.mul(expansionR);
            mintAmount = amount.mul(mintFactor.sub(10000)).div(10000);
        } else {
            mintAmount = amount.mul(Constants.getBaseExpansionFactor()).div(10000);
        }
        return (mintAmount.mul(Constants.getStabilizerFee()).div(10000),mintAmount.mul(Constants.getTreasuryFee()).div(10000),mintAmount);
    }

    function getBurnValues(address recipient, uint256 amount) internal view returns(uint256, uint256) {
        uint256 currentFactor = getFactor();
        uint256 contractionR;
        if (isSupportedPool(recipient)) {
            contractionR = (_poolCounters[recipient].tokenBalance).mul(_poolCounters[recipient].startPairTokenBalance).mul(100).div(_poolCounters[recipient].pairTokenBalance).div(_poolCounters[recipient].startTokenBalance);
        } else {
            contractionR = (_poolCounters[_mainPool].tokenBalance).mul(_poolCounters[_mainPool].startPairTokenBalance).mul(100).div(_poolCounters[_mainPool].pairTokenBalance).div(_poolCounters[_mainPool].startTokenBalance);
        }
        uint256 burnAmount;
        if (contractionR > (Constants.getBaseContractionFactor().add(10000)).div(100)) {
            uint256 burnFactor = contractionR.mul(contractionR);
            burnAmount = amount.mul(burnFactor.sub(10000)).div(10000);
            if (burnAmount > amount.mul(Constants.getBaseContractionCap()).div(10000)) burnAmount = amount.mul(Constants.getBaseContractionCap()).div(10000);
        } else {
            burnAmount = amount.mul(Constants.getBaseContractionFactor()).div(10000);
        }
        return (burnAmount, burnAmount.mul(currentFactor));
    }

    function getUtilityFee(uint256 amount) internal view returns(uint256, uint256) {
        uint256 currentFactor = getFactor();
        uint256 utilityFee = amount.mul(Constants.getBaseUtilityFee()).div(10000);
        return (utilityFee, utilityFee.mul(currentFactor));
    }
    function getMintRate(address pool) external view returns (uint256) {
        uint256 expansionR = (_poolCounters[pool].pairTokenBalance).mul(_poolCounters[pool].startTokenBalance).mul(100).div(_poolCounters[pool].startPairTokenBalance).div(_poolCounters[pool].tokenBalance);
        if (expansionR > (Constants.getBaseExpansionFactor()).add(10000).div(100)) {
            uint256 mintFactor = expansionR.mul(expansionR);
            return mintFactor.sub(10000);
        } else {
            return Constants.getBaseExpansionFactor();
        }
    }
    function getBurnRate(address pool) external view returns (uint256) {
        uint256 contractionR = (_poolCounters[pool].tokenBalance).mul(_poolCounters[pool].startPairTokenBalance).mul(100).div(_poolCounters[pool].pairTokenBalance).div(_poolCounters[pool].startTokenBalance);
        uint256 burnRate;
        if (contractionR > (Constants.getBaseContractionFactor().add(10000)).div(100)) {
            uint256 burnFactor = contractionR.mul(contractionR);
            burnRate = burnFactor.sub(10000);
            if (burnRate > Constants.getBaseContractionCap()) {
                return Constants.getBaseContractionCap();
            }
            return burnRate;

        } else {
            return Constants.getBaseContractionFactor();
        }
    }
}
contract Setters is State, Getters {
    function updatePresaleAddress(address presaleAddress) internal {
        _presaleCon = presaleAddress;
    }
    function setAllowances(address owner, address spender, uint256 amount) internal {
        _allowances[owner][spender] = amount;
    }
    function addToAccount(address account, uint256 amount) internal {
        uint256 currentFactor = getFactor();
        uint256 largeAmount = amount.mul(currentFactor);
        _largeBalances[account] = _largeBalances[account].add(largeAmount);
        _totalSupply = _totalSupply.add(amount);
    }
    function addToAll(uint256 amount) internal {
        _totalSupply = _totalSupply.add(amount);
    }
    function initializeEpoch() internal {
        _currentEpoch = now;
    }
    function updateEpoch() internal {
        initializeEpoch();
        for (uint256 i=0; i<_supportedPools.length; i++) {
            _poolCounters[_supportedPools[i]].startTokenBalance = _poolCounters[_supportedPools[i]].tokenBalance;
            _poolCounters[_supportedPools[i]].startPairTokenBalance = _poolCounters[_supportedPools[i]].pairTokenBalance;
        }
    }
    function initializeLargeTotal() internal {
        _largeTotal = Constants.getLargeTotal();
    }
    function syncPair(address pool) internal returns(bool) {
        (uint256 tokenBalance, uint256 pairTokenBalance, uint256 lpBalance) = getUpdatedPoolCounters(pool, _poolCounters[pool].pairToken);
        bool lpBurn = lpBalance < _poolCounters[pool].lpBalance;
        _poolCounters[pool].lpBalance = lpBalance;
        _poolCounters[pool].tokenBalance = tokenBalance;
        _poolCounters[pool].pairTokenBalance = pairTokenBalance;
        return (lpBurn);
    }
    function silentSyncPair(address pool) public {
        (uint256 tokenBalance, uint256 pairTokenBalance, uint256 lpBalance) = getUpdatedPoolCounters(pool, _poolCounters[pool].pairToken);
        _poolCounters[pool].lpBalance = lpBalance;
        _poolCounters[pool].tokenBalance = tokenBalance;
        _poolCounters[pool].pairTokenBalance = pairTokenBalance;
    }
    function addSupportedPool(address pool, address pairToken) internal {
        require(!isSupportedPool(pool),"This pool is already supported");
        _isSupportedPool[pool] = true;
        _supportedPools.push(pool);
        (uint256 tokenBalance, uint256 pairTokenBalance, uint256 lpBalance) = getUpdatedPoolCounters(pool, pairToken);
        _poolCounters[pool] = PoolCounter(pairToken, tokenBalance, pairTokenBalance, lpBalance, tokenBalance, pairTokenBalance);
    }
    function removeSupportedPool(address pool) internal {
        require(isSupportedPool(pool), "This pool is currently not supported");
        for (uint256 i = 0; i < _supportedPools.length; i++) {
            if (_supportedPools[i] == pool) {
                _supportedPools[i] = _supportedPools[_supportedPools.length - 1];
                _isSupportedPool[pool] = false;
                delete _poolCounters[pool];
                _supportedPools.pop();
                break;
            }
        }
    }
}
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract Stabilizer {
    constructor() public {

    }
}
contract YFStable is Setters, Context, IERC20, Ownable {
    using SafeMath for uint256;
    using Address for address;

    modifier onlyTaxless {
        require(isTaxlessSetter(_msgSender()),"not taxless");
        _;
    }
    modifier onlyPresale {
        require(_msgSender()==getPresaleAddress(),"not presale");
        require(!isPresaleDone(), "Presale over");
        _;
    }
    modifier pausable {
        require(!isPaused(), "Paused");
        _;
    }
    modifier taxlessTx {
        _taxLess = true;
        _;
        _taxLess = false;
    }

    constructor() public {
        updateEpoch();
        initializeLargeTotal();
        setStabilizer(address(new Stabilizer()));
    }

    function name() public view returns (string memory) {
        return Constants.getName();
    }
    
    function symbol() public view returns (string memory) {
        return Constants.getSymbol();
    }
    
    function decimals() public view returns (uint8) {
        return Constants.getDecimals();
    }
    
    function totalSupply() public view override returns (uint256) {
        return getTotalSupply();
    }
    
    function circulatingSupply() public view returns (uint256) {
        uint256 currentFactor = getFactor();
        return getTotalSupply().sub(getTotalLockedBalance().div(currentFactor)).sub(balanceOf(address(this))).sub(balanceOf(getStabilizer()));
    }
    
    function balanceOf(address account) public view override returns (uint256) {
        uint256 currentFactor = getFactor();
        if (hasLockedBalance(account)) return (getLargeBalances(account).add(getLockedBalance(account)).div(currentFactor));
        return getLargeBalances(account).div(currentFactor);
    }
    
    function unlockedBalanceOf(address account) public view returns (uint256) {
        uint256 currentFactor = getFactor();
        return getLargeBalances(account).div(currentFactor); 
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view override returns (uint256) {
        return getAllowances(owner,spender);
    }

    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), getAllowances(sender,_msgSender()).sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, getAllowances(_msgSender(),spender).add(addedValue));
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, getAllowances(_msgSender(),spender).sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    function mint(address to, uint256 amount) public onlyPresale {
        addToAccount(to,amount);
        emit Transfer(address(0),to,amount);
    }

    function _approve(address owner, address spender, uint256 amount) private {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        setAllowances(owner, spender, amount);
        emit Approval(owner, spender, amount);
    }

    function _transfer(address sender, address recipient, uint256 amount) private pausable {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "Amount must be greater than zero");
        require(amount <= balanceOf(sender),"Amount exceeds balance");
        require(amount <= unlockedBalanceOf(sender),"Amount exceeds unlocked balance");
        require(isPresaleDone(),"Presale yet to close");
        if (now > getCurrentEpoch().add(Constants.getEpochLength())) updateEpoch();
        uint256 currentFactor = getFactor();
        uint256 largeAmount = amount.mul(currentFactor);
        uint256 txType;
        if (isTaxLess()) {
            txType = 3;
        } else {
            bool lpBurn;
            if (isSupportedPool(sender)) {
                lpBurn = syncPair(sender);
            } else if (isSupportedPool(recipient)){
                silentSyncPair(recipient);
            } else {
                silentSyncPair(_mainPool);
            }
            txType = _getTxType(sender, recipient, lpBurn);
        }
        // Buy Transaction from supported pools - requires mint, no utility fee
        if (txType == 1) {
            (uint256 stabilizerMint, uint256 treasuryMint, uint256 totalMint) = getMintValue(sender, amount);
            // uint256 mintSize = amount.div(100);
            _largeBalances[sender] = _largeBalances[sender].sub(largeAmount);
            _largeBalances[recipient] = _largeBalances[recipient].add(largeAmount);
            _largeBalances[getStabilizer()] = _largeBalances[getStabilizer()].add(stabilizerMint.mul(currentFactor));
            _largeBalances[Constants.getTreasuryAdd()] = _largeBalances[Constants.getTreasuryAdd()].add(treasuryMint.mul(currentFactor));
            _totalSupply = _totalSupply.add(totalMint);
            emit Transfer(sender, recipient, amount);
            emit Transfer(address(0),getStabilizer(),stabilizerMint);
            emit Transfer(address(0),Constants.getTreasuryAdd(),treasuryMint);
        }
        // Sells to supported pools or unsupported transfer - requires exit burn and utility fee
        else if (txType == 2) {
            (uint256 burnSize, uint256 largeBurnSize) = getBurnValues(recipient, amount);
            (uint256 utilityFee, uint256 largeUtilityFee) = getUtilityFee(amount);
            uint256 actualTransferAmount = amount.sub(burnSize).sub(utilityFee);
            uint256 largeTransferAmount = actualTransferAmount.mul(currentFactor);
            _largeBalances[sender] = _largeBalances[sender].sub(largeAmount);
            _largeBalances[recipient] = _largeBalances[recipient].add(largeTransferAmount);
            _largeBalances[_liquidityReserve] = _largeBalances[_liquidityReserve].add(largeUtilityFee);
            _totalSupply = _totalSupply.sub(burnSize);
            _largeTotal = _largeTotal.sub(largeBurnSize);
            emit Transfer(sender, recipient, actualTransferAmount);
            emit Transfer(sender, address(0), burnSize);
            emit Transfer(sender, _liquidityReserve, utilityFee);
        } 
        // Add Liquidity via interface or Remove Liquidity Transaction to supported pools - no fee of any sort
        else if (txType == 3) {
            _largeBalances[sender] = _largeBalances[sender].sub(largeAmount);
            _largeBalances[recipient] = _largeBalances[recipient].add(largeAmount);
            emit Transfer(sender, recipient, amount);
        }
    }

    function _getTxType(address sender, address recipient, bool lpBurn) private returns(uint256) {
        uint256 txType = 2;
        if (isSupportedPool(sender)) {
            if (lpBurn) {
                txType = 3;
            } else {
                txType = 1;
            }
        } else if (sender == Constants.getRouterAdd()) {
            txType = 3;
        }
        return txType;
    }

    function setPresale(address presaleAdd) external onlyOwner() {
        require(!isPresaleDone(), "Presale is already completed");
        updatePresaleAddress(presaleAdd);
    }

    function setPresaleDone() public payable onlyPresale {
        require(totalSupply() <= Constants.getLaunchSupply(), "Total supply is already minted");
        _mintRemaining();
        _presaleDone = true;
        _createEthPool();
    }

    function _mintRemaining() private {
        require(!isPresaleDone(), "Cannot mint post presale");
        Constants.getDeployerAdd().transfer(address(this).balance.div(3));
        uint256 toMint = Constants.getLaunchSupply().sub(totalSupply());
        uint256 tokensToAdd = address(this).balance.div(10**11).mul(Constants.getListingRate());
        if(toMint > tokensToAdd) {
            addToAccount(address(0),toMint.sub(tokensToAdd));
            emit Transfer(address(0),address(0),toMint.sub(tokensToAdd));
        }
        addToAccount(address(this), tokensToAdd);
        emit Transfer(address(0),address(this),tokensToAdd);
    }

    function mintLockedTranche(address account, uint256 unlockTime, uint256 amount) external onlyOwner() {
        require(!isPresaleDone(), "Cannot mint post presale");
        uint256 currentFactor = getFactor();
        uint256 largeAmount = amount.mul(currentFactor);
        _lockBoxes.push(LockBox(account, largeAmount, unlockTime, true));
        _lockedBalance[account] = _lockedBalance[account].add(largeAmount);
        _hasLockedBalance[account] = true;
        _totalLockedBalance = _totalLockedBalance.add(largeAmount);
        _totalSupply = _totalSupply.add(amount);
        emit Transfer(address(0),account,amount);
    }
    
    function mintUnlockedTranche(address account, uint256 amount) external onlyOwner() {
        require(!isPresaleDone(), "Cannot mint post presale");
        addToAccount(account, amount);
        emit Transfer(address(0),account,amount);
    }

    function unlockTranche(uint256 tranche) external {
        require(hasLockedBalance(_msgSender()),"Caller has no locked balance");
        (address beneficiary, uint256 balance, uint256 unlockTime, bool locked) = getLockBoxes(tranche);
        require(unlockTime <= now,"This tranche cannot be unlocked yet");
        require(beneficiary == _msgSender(),"You are not the owner of this tranche");
        require(locked ==  true, "This tranche has already been unlocked");
        _totalLockedBalance = _totalLockedBalance.sub(balance);
        _largeBalances[_msgSender()] = _largeBalances[_msgSender()].add(balance);
        _lockedBalance[_msgSender()] = _lockedBalance[_msgSender()].sub(balance);
        if (_lockedBalance[_msgSender()] <= 0) _hasLockedBalance[_msgSender()] = false;
        _lockBoxes[tranche].lockedBalance = 0;
        _lockBoxes[tranche].locked = false;
    }

    function reassignTranche(uint256 tranche, address beneficiary) external onlyOwner() {
        (address oldBeneficiary, uint256 balance, uint256 unlockTime, bool locked) = getLockBoxes(tranche);
        require(locked == true, "This tranche has already been unlocked");
        require(unlockTime > now,"This tranche has already been vested");
        _lockedBalance[oldBeneficiary] = _lockedBalance[oldBeneficiary].sub(balance);
        _lockedBalance[beneficiary] = _lockedBalance[beneficiary].add(balance);
        if (_lockedBalance[oldBeneficiary] == 0) _hasLockedBalance[oldBeneficiary] = false;
        _hasLockedBalance[beneficiary] = true; 
        _lockBoxes[tranche].beneficiary = beneficiary;
        uint256 currentFactor = getFactor();
        emit Transfer(oldBeneficiary,beneficiary,balance.div(currentFactor));
    }

    function _createEthPool() private taxlessTx {
        IUniswapV2Router02 uniswapRouterV2 = getUniswapRouter();
        IUniswapV2Factory uniswapFactory = getUniswapFactory();
        address tokenUniswapPair;
        if (uniswapFactory.getPair(address(uniswapRouterV2.WETH()), address(this)) == address(0)) {
            tokenUniswapPair = uniswapFactory.createPair(
            address(uniswapRouterV2.WETH()), address(this));
        } else {
            tokenUniswapPair = uniswapFactory.getPair(address(this),uniswapRouterV2.WETH());
        }
        uint256 tokensToAdd = balanceOf(address(this));        
        _approve(address(this), 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D, tokensToAdd);
        uniswapRouterV2.addLiquidityETH{value: address(this).balance}(address(this),
           tokensToAdd, 0, 0, Constants.getDeployerAdd(), block.timestamp);
        addSupportedPool(tokenUniswapPair, address(uniswapRouterV2.WETH()));
        _mainPool = tokenUniswapPair;
    }

    function createTokenPool(address pairToken, uint256 amount) external onlyOwner() taxlessTx {
        IUniswapV2Router02 uniswapRouterV2 = getUniswapRouter();
        IUniswapV2Factory uniswapFactory = getUniswapFactory();
        address tokenUniswapPair;
        if (uniswapFactory.getPair(pairToken, address(this)) == address(0)) {
            tokenUniswapPair = uniswapFactory.createPair(
            pairToken, address(this));
        } else {
            tokenUniswapPair = uniswapFactory.getPair(pairToken,address(this));
        }
        require(uniswapFactory.getPair(pairToken,address(uniswapRouterV2.WETH())) != address(0), "Eth pairing does not exist");
        require(balanceOf(address(this)) >= amount, "Amount exceeds the token balance");
        uint256 toConvert = amount.div(2);
        uint256 toAdd = amount.sub(toConvert);
        uint256 initialBalance = IERC20(pairToken).balanceOf(address(this));
        address[] memory path = new address[](3);
        path[0] = address(this);
        path[1] = uniswapRouterV2.WETH();
        path[2] = pairToken;
        _approve(address(this), address(uniswapRouterV2), toConvert);
        uniswapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            toConvert, 0, path, address(this), block.timestamp);
        uint256 newBalance = IERC20(pairToken).balanceOf(address(this)).sub(initialBalance);
        _approve(address(this), address(uniswapRouterV2), toAdd);
        IERC20(pairToken).approve(address(uniswapRouterV2), newBalance);
        uniswapRouterV2.addLiquidity(address(this),pairToken,toAdd,newBalance,0,0,address(this),block.timestamp);
        addSupportedPool(tokenUniswapPair, pairToken);
    }

    function addNewSupportedPool(address pool, address pairToken) external onlyOwner() {
        addSupportedPool(pool, pairToken);
    }

    function removeOldSupportedPool(address pool) external onlyOwner() {
        removeSupportedPool(pool);
    }

    function setTaxlessSetter(address cont) external onlyOwner() {
        require(!isTaxlessSetter(cont),"already setter");
        _isTaxlessSetter[cont] = true;
    }

    function setTaxless(bool flag) public onlyTaxless {
        _taxLess = flag;
    }

    function removeTaxlessSetter(address cont) external onlyOwner() {
        require(isTaxlessSetter(cont),"not setter");
        _isTaxlessSetter[cont] = false;
    }

    function setLiquidityReserve(address reserve) external onlyOwner() {
        require(Address.isContract(reserve),"Need a contract");
        _isTaxlessSetter[_liquidityReserve] = false;
        uint256 oldBalance = balanceOf(_liquidityReserve);
        if (oldBalance > 0) {
            _transfer(_liquidityReserve, reserve, oldBalance);
            emit Transfer(_liquidityReserve, reserve, oldBalance);
        }
        _liquidityReserve = reserve;
        _isTaxlessSetter[reserve] = true;
    }

    function setStabilizer(address reserve) public onlyOwner() taxlessTx {
        require(Address.isContract(reserve),"Need a contract");
        _isTaxlessSetter[_stabilizer] = false;
        uint256 oldBalance = balanceOf(_stabilizer);
        if (oldBalance > 0) {
            _transfer(_stabilizer, reserve, oldBalance);
            emit Transfer(_stabilizer, reserve, oldBalance);
        }
        _stabilizer = reserve;
        _isTaxlessSetter[reserve] = true;
    }
    
    function pauseContract(bool flag) external onlyOwner() {
        _paused = flag;
    }

}