// Contract: PoolStakeFactory
// Target function: function withdrawLiquidity(uint _lpAmount) external returns(bool)
 Multiplier(_multiplier2);
        
        emit NewUToken(msg.sender, _uToken2, _multiplier2);
        return true;
    }
    
    function completeUTokenMerge() external onlyPlatformWallet returns(bool) {
        
        require(!migratedToLQDY, "already migrated to LQDY");
        
        migratedToLQDY = true;
        uToken1 = uToken2;
        
        address _multiplier2 = address(multiplier2);
        multiplier1 = Multiplier(_multiplier2);
        
        emit MigratedToLQDY(msg.sender, uToken2, address(multiplier2));
        return true;
    }
    
    function changeFeeReceiver(address _feeReceiver) external onlyPlatformWallet returns(bool) {
        
        platformWallet = _feeReceiver;
        
        emit FeeReceiverUpdated(msg.sender, _feeReceiver);
        return true;
    }
    
    /* 
     * @dev change the return percentage for locking up liquidity for ETH and Token (only Owner).
     * ------------------------------------------------------------------------------------
     * @param _period1RPETH - _period4RPToken --> the new return percentages.
     * ----------------------------------------------
     * returns whether successfully changed or not.
     */ 
    function changeReturnPercentages(
        uint _period1RPETH, uint _period2RPETH, uint _period3RPETH, uint _period4RPETH, 
        uint _period1RPToken, uint _period2RPToken, uint _period3RPToken, uint _period4RPToken
    ) external onlyOwner returns(bool) {
        
        period1RPWeth = _period1RPETH;
        period2RPWeth = _period2RPETH;
        period3RPWeth = _period3RPETH;
        period4RPWeth = _period4RPETH;
        
        period1RPToken = _period1RPToken;
        period2RPToken = _period2RPToken;
        period3RPToken = _period3RPToken;
        period4RPToken = _period4RPToken;
        
        return true;
    }
    
    /* 
     * @dev change the lockup periods (only Owner).
     * ------------------------------------------------------------------------------------
     * @param _firstTerm - _fourthTerm --> the new term periods.
     * ----------------------------------------------
     * returns whether successfully changed or not.
     */ 
    function changeTermPeriods(
        uint32 _firstTerm, uint32 _secondTerm, 
        uint32 _thirdTerm, uint32 _fourthTerm
    ) external onlyOwner returns(bool) {
        
        period1 = _firstTerm;
        period2 = _secondTerm;
        period3 = _thirdTerm;
        period4 = _fourthTerm;
        
        return true;
    }
    
    /* 
     * @dev change the maximum ETH that a user can enter with (only Owner).
     * ------------------------------------------------------------------------------------
     * @param _cap      --> the new cap value.
     * ----------------------------------------------
     * returns whether successfully changed or not.
     */ 
    function changeCap(uint _cap) external onlyOwner returns(bool) {
        
        cap = _cap;
        
        emit CapUpdated(msg.sender, _cap);
        return true;
    }
    
    /* 
     * @dev makes migration possible for uniswap V3 (only Owner).
     * ----------------------------------------------------------
     * @param _unistakeMigrationContract      --> the migration contract address.
     * -------------------------------------------------------------------------
     * returns whether successfully migrated or not.
     */ 
    function allowMigration(address _unistakeMigrationContract) external onlyOwner returns (bool) {
        
        require(_unistakeMigrationContract != address(0x0), "cannot migrate to a null address");
        migrationContract = _unistakeMigrationContract;
        
        emit VersionMigrated(msg.sender, now, migrationContract);
        return true;
    }
    
    /* 
     * @dev initiates migration for a user (only when migration is allowed).
     * -------------------------------------------------------------------
     * @param _unistakeMigrationContract      --> the migration contract address.
     * -------------------------------------------------------------------------
     * returns whether successfully migrated or not.
     */ 
    function startMigration(address _unistakeMigrationContract) external returns (bool) {
        
        require(_unistakeMigrationContract != address(0x0), "cannot migrate to a null address");
        require(migrationContract == _unistakeMigrationContract, "must confirm endpoint");
        require(!getUserMigration(msg.sender), "must not be migrated already");
        
        _users[msg.sender].migrated = true;
        
        uint256 liquidity = _users[msg.sender].liquidity;
        lpToken.transfer(migrationContract, liquidity);
        
        emit LiquidityMigrated(msg.sender, liquidity, migrationContract);
        return true;
    }
    
    /* 
     * @dev add more staking bonuses to the pool.
     * ----------------------------------------
     * @param              --> input value along with call to add ETH
     * @param _tokenAmount --> the amount of token to be added.
     * --------------------------------------------------------
     * returns whether successfully added or not.
     */ 
    function addBonus(uint _tokenAmount) external payable returns(bool) {
        
        require(_tokenAmount > 0 || msg.value > 0, "must send value");
        if (_tokenAmount > 0)
        require(token.transferFrom(msg.sender, address(this), _tokenAmount), "must approve smart contract");
        
        emit BonusAdded(msg.sender, msg.value, _tokenAmount);
        return true;
    }
    
    /* 
     * @dev remove staking bonuses to the pool. (only owner)
     * must have enough asset to be removed
     * ----------------------------------------
     * @param _amountETH   --> eth amount to be removed
     * @param _amountToken --> token amount to be removed.
     * --------------------------------------------------------
     * returns whether successfully added or not.
     */ 
    function removeETHAndTokenBouses(uint _amountETH, uint _amountToken) external onlyOwner returns (bool success) {
       
        require(_amountETH > 0 || _amountToken > 0, "amount must be larger than zero");
    
        if (_amountETH > 0) {
            require(_checkForSufficientStakingBonusesForETH(_amountETH), 'cannot withdraw above current ETH bonus balance');
            msg.sender.transfer(_amountETH);
            emit BonusRemoved(msg.sender, _amountETH);
        }
        
        if (_amountToken > 0) {
            require(_checkForSufficientStakingBonusesForToken(_amountToken), 'cannot withdraw above current token bonus balance');
            require(token.transfer(msg.sender, _amountToken), "error: token transfer failed");
            emit BonusRemoved(msg.sender, _amountToken);
        }
        
        return true;
    }
    
    /* 
     * @dev add unwrapped liquidity to staking pool.
     * --------------------------------------------
     * @param _tokenAmount  --> must input token amount along with call
     * @param _term         --> the lockup term.
     * @param _multiplier   --> whether multiplier should be used or not
     *                        1 means you want to use the multiplier. !1 means no multiplier
     * -------------------------------------------------------------------------------------
     */
    function addLiquidity(uint _term, uint _multiplier, uint _id) external uTokenVet(_id) payable {
        
        require(!getUserMigration(msg.sender), "must not be migrated already");
        require(now >= _users[msg.sender].release, "cannot override current term");
        
        (bool isValid, uint period) = _isValidTerm(_term);
        require(isValid, "must select a valid term");
        require(msg.value > 0, "must send ETH along with transaction");
        if (cap != 0) require(msg.value <= cap, "cannot provide more than the cap");
        
        uint rate = _proportion(msg.value, address(weth), address(token));
        require(token.transferFrom(msg.sender, address(this), rate), "must approve smart contract");
        
        (uint ETH_bonus, uint token_bonus) = getUserBonusPending(msg.sender);
        require(ETH_bonus == 0 && token_bonus == 0, "must first withdraw available bonus");
        
        uint oneTenthOfRate = (rate.mul(10)).div(100);
        token.approve(address(uniswapRouter), rate);

        (uint amountToken, uint amountETH, uint liquidity) = 
        uniswapRouter.addLiquidityETH{value: msg.value}(
            address(token), 
            rate.add(oneTenthOfRate), 
            0, 
            0, 
            address(this), 
            now.add(_012_HOURS_IN_SECONDS));
        
        uint term = _term;
        uint mul = _multiplier;
        uint __id = _id;
        
        _users[msg.sender].start = now;
        _users[msg.sender].release = now.add(term);
        
        totalETHProvided = totalETHProvided.add(amountETH);
        totalTokenProvided = totalTokenProvided.add(amountToken);
        totalProviders++;
        
        uint currentPeriod = _users[msg.sender].period;
        if (currentPeriod != period) {
            _providers[currentPeriod]--;
            _providers[period]++;
            _users[msg.sender].period = period;
        }
        
        uint previousLiquidity = _users[msg.sender].liquidity; 
        _users[msg.sender].liquidity = previousLiquidity.add(liquidity);  
        
        uint wethRP = _calculateReturnPercentage(weth, term);
        uint tokenRP = _calculateReturnPercentage(token, term);
               
        (uint provided_ETH, uint provided_token) = getUserLiquidity(msg.sender);
        
        if (mul == 1) 
        _withMultiplier(term, provided_ETH, provided_token, wethRP, tokenRP, __id);
        else _withoutMultiplier(provided_ETH, provided_token, wethRP, tokenRP);
        
        emit LiquidityAdded(msg.sender, liquidity, amountETH, amountToken);
    }
    
    /* 
     * @dev uses the Multiplier contract for double rewarding
     * ------------------------------------------------------
     * @param _term       --> the lockup term.
     * @param amountETH   --> ETH amount provided in liquidity
     * @param amountToken --> token amount provided in liquidity
     * @param wethRP      --> return percentge for ETH based on term period
     * @param tokenRP     --> return percentge for token based on term period
     * --------------------------------------------------------------------
     */
    function _withMultiplier(
        uint _term, uint amountETH, uint amountToken, uint wethRP, uint tokenRP, uint _id
    ) internal {
        
        if(_id == 1) 
        _resolveMultiplier(_term, amountETH, amountToken, wethRP, tokenRP, multiplier1);
        else _resolveMultiplier(_term, amountETH, amountToken, wethRP, tokenRP, multiplier2);
    }
    
    /* 
     * @dev distributes bonus without considering Multiplier
     * ------------------------------------------------------
     * @param amountETH   --> ETH amount provided in liquidity
     * @param amountToken --> token amount provided in liquidity
     * @param wethRP      --> return percentge for ETH based on term period
     * @param tokenRP     --> return percentge for token based on term period
     * --------------------------------------------------------------------
     */
    function _withoutMultiplier(
        uint amountETH, uint amountToken, uint wethRP, uint tokenRP
    ) internal {
            
        uint addedBonusWeth;
        uint addedBonusToken;
        
        if (_offersBonus(weth) && _offersBonus(token)) {
            
            addedBonusWeth = _calculateBonus(amountETH, wethRP);
            addedBonusToken = _calculateBonus(amountToken, tokenRP);
                
            require(_checkForSufficientStakingBonusesForETH(addedBonusWeth)
            && _checkForSufficientStakingBonusesForToken(addedBonusToken),
            "must be sufficient staking bonuses available in pool");
                        
            _users[msg.sender].wethBonus = _users[msg.sender].wethBonus.add(addedBonusWeth);
            _users[msg.sender].tokenBonus = _users[msg.sender].tokenBonus.add(addedBonusToken);
            _pendingBonusesWeth = _pendingBonusesWeth.add(addedBonusWeth);
            _pendingBonusesToken = _pendingBonusesToken.add(addedBonusToken);
                
        } else if (_offersBonus(weth) && !_offersBonus(token)) {
                
            addedBonusWeth = _calculateBonus(amountETH, wethRP);
                
            require(_checkForSufficientStakingBonusesForETH(addedBonusWeth), 
            "must be sufficient staking bonuses available in pool");
                
            _users[msg.sender].wethBonus = _users[msg.sender].wethBonus.add(addedBonusWeth);
            _pendingBonusesWeth = _pendingBonusesWeth.add(addedBonusWeth);
                
        } else if (!_offersBonus(weth) && _offersBonus(token)) {
                
            addedBonusToken = _calculateBonus(amountToken, tokenRP);
                
            require(_checkForSufficientStakingBonusesForToken(addedBonusToken),
            "must be sufficient staking bonuses available in pool");
                
            _users[msg.sender].tokenBonus = _users[msg.sender].tokenBonus.add(addedBonusToken);
            _pendingBonusesToken = _pendingBonusesToken.add(addedBonusToken);
        }
        
    }
    
    function _resolveMultiplier(
        uint _term, uint amountETH, uint amountToken, uint wethRP, uint tokenRP, Multiplier _multiplier
    ) internal {
        
        uint addedBonusWeth;
        uint addedBonusToken;
        
        require(_multiplier.balance(msg.sender) > 0, "No Multiplier balance to use");
        if (_term > _multiplier.lockupPeriod(msg.sender)) _multiplier.updateLockupPeriod(msg.sender, _term);
            
        uint multipliedETH = _proportion(_multiplier.balance(msg.sender), uToken1, address(weth));
        uint multipliedToken = _proportion(multipliedETH, address(weth), address(token));
            
        if (_offersBonus(weth) && _offersBonus(token)) {
                        
            if (multipliedETH > amountETH) {
                multipliedETH = (_calculateBonus((amountETH.mul(_multiplier.getMultiplierCeiling())), wethRP));
                addedBonusWeth = multipliedETH;
            } else {
                addedBonusWeth = (_calculateBonus((amountETH.add(multipliedETH)), wethRP));
            }
                
            if (multipliedToken > amountToken) {
                multipliedToken = (_calculateBonus((amountToken.mul(_multiplier.getMultiplierCeiling())), tokenRP));
                addedBonusToken = multipliedToken;
            } else {
                addedBonusToken = (_calculateBonus((amountToken.add(multipliedToken)), tokenRP));
            }
                    
            require(_checkForSufficientStakingBonusesForETH(addedBonusWeth)
            && _checkForSufficientStakingBonusesForToken(addedBonusToken),
            "must be sufficient staking bonuses available in pool");
                                
            _users[msg.sender].wethBonus = _users[msg.sender].wethBonus.add(addedBonusWeth);
            _users[msg.sender].tokenBonus = _users[msg.sender].tokenBonus.add(addedBonusToken);
            _pendingBonusesWeth = _pendingBonusesWeth.add(addedBonusWeth);
            _pendingBonusesToken = _pendingBonusesToken.add(addedBonusToken);
                    
        } else if (_offersBonus(weth) && !_offersBonus(token)) {
                    
            if (multipliedETH > amountETH) {
                multipliedETH = (_calculateBonus((amountETH.mul(_multiplier.getMultiplierCeiling())), wethRP));
                addedBonusWeth = multipliedETH;
            } else {
                addedBonusWeth = (_calculateBonus((amountETH.add(multipliedETH)), wethRP));
            }
                        
            require(_checkForSufficientStakingBonusesForETH(addedBonusWeth), 
            "must be sufficient staking bonuses available in pool");
                    
            _users[msg.sender].wethBonus = _users[msg.sender].wethBonus.add(addedBonusWeth);
            _pendingBonusesWeth = _pendingBonusesWeth.add(addedBonusWeth);
                        
        } else if (!_offersBonus(weth) && _offersBonus(token)) {
            
            if (multipliedToken > amountToken) {
                multipliedToken = (_calculateBonus((amountToken.mul(_multiplier.getMultiplierCeiling())), tokenRP));
                addedBonusToken = multipliedToken;
            } else {
                addedBonusToken = (_calculateBonus((amountToken.add(multipliedToken)), tokenRP));
            }
            
            require(_checkForSufficientStakingBonusesForToken(addedBonusToken),
            "must be sufficient staking bonuses available in pool");
         
            _users[msg.sender].tokenBonus = _users[msg.sender].tokenBonus.add(addedBonusToken);
            _pendingBonusesToken = _pendingBonusesToken.add(addedBonusToken);
        }
    }    
    
    /* 
     * @dev relocks liquidity already provided
     * --------------------------------------------
     * @param _term       --> the lockup term.
     * @param _multiplier --> whether multiplier should be used or not
     *                        1 means you want to use the multiplier. !1 means no multiplier
     * --------------------------------------------------------------
     * returns whether successfully relocked or not.
     */
    function relockLiquidity(uint _term, uint _multiplier, uint _id) external uTokenVet(_id) returns(bool) {
        
        require(!getUserMigration(msg.sender), "must not be migrated already");
        require(_users[msg.sender].liquidity > 0, "do not have any liquidity to lock");
        require(now >= _users[msg.sender].release, "cannot override current term");
        (bool isValid, uint period) = _isValidTerm(_term);
        require(isValid, "must select a valid term");
        
        (uint ETH_bonus, uint token_bonus) = getUserBonusPending(msg.sender);
        require (ETH_bonus == 0 && token_bonus == 0, 'must withdraw available bonuses first');
        
        (uint provided_ETH, uint provided_token) = getUserLiquidity(msg.sender);
        if (cap != 0) require(provided_ETH <= cap, "cannot provide more than the cap");
        
        uint wethRP = _calculateReturnPercentage(weth, _term);
        uint tokenRP = _calculateReturnPercentage(token, _term);
        
        totalProviders++;
        
        uint currentPeriod = _users[msg.sender].period;
        if (currentPeriod != period) {
            _providers[currentPeriod]--;
            _providers[period]++;
            _users[msg.sender].period = period;
        }
        
        _users[msg.sender].start = now;
        _users[msg.sender].release = now.add(_term);
        
        uint __id = _id;
        
        if (_multiplier == 1) 
        _withMultiplier(_term, provided_ETH, provided_token, wethRP, tokenRP, __id);
        else _withoutMultiplier(provided_ETH, provided_token, wethRP, tokenRP); 
        
        return true;
    }