// Contract: StabilityPool
// Target function: function getFrontEndRBSTGain(address _frontEnd) public view override returns (uint)
EndStake = compoundedFrontEndStake.add(_amount);
        _updateFrontEndStakeAndSnapshots(frontEnd, newFrontEndStake);
        emit FrontEndStakeChanged(frontEnd, newFrontEndStake, msg.sender);

        _sendRUBCtoStabilityPool(msg.sender, _amount);

        uint newDeposit = compoundedRUBCDeposit.add(_amount);
        _updateDepositAndSnapshots(msg.sender, newDeposit);
        emit UserDepositChanged(msg.sender, newDeposit);

        emit ETHGainWithdrawn(msg.sender, depositorETHGain, RUBCLoss); // RUBC Loss required for event log

        _sendETHGainToDepositor(depositorETHGain);
     }

    /*  withdrawFromSP():
    *
    * - Triggers a RBST issuance, based on time passed since the last issuance. The RBST issuance is shared between *all* depositors and front ends
    * - Removes the deposit's front end tag if it is a full withdrawal
    * - Sends all depositor's accumulated gains (RBST, ETH) to depositor
    * - Sends the tagged front end's accumulated RBST gains to the tagged front end
    * - Decreases deposit and tagged front end's stake, and takes new snapshots for each.
    *
    * If _amount > userDeposit, the user withdraws all of their compounded deposit.
    */
    function withdrawFromSP(uint _amount) external override {
        if (_amount !=0) {_requireNoUnderCollateralizedTroves();}
        uint initialDeposit = deposits[msg.sender].initialValue;
        _requireUserHasDeposit(initialDeposit);

        ICommunityIssuance communityIssuanceCached = communityIssuance;

        _triggerRBSTIssuance(communityIssuanceCached);

        uint depositorETHGain = getDepositorETHGain(msg.sender);

        uint compoundedRUBCDeposit = getCompoundedRUBCDeposit(msg.sender);
        uint RUBCtoWithdraw = LiquityMath._min(_amount, compoundedRUBCDeposit);
        uint RUBCLoss = initialDeposit.sub(compoundedRUBCDeposit); // Needed only for event log

        // First pay out any RBST gains
        address frontEnd = deposits[msg.sender].frontEndTag;
        _payOutRBSTGains(communityIssuanceCached, msg.sender, frontEnd);
        
        // Update front end stake
        uint compoundedFrontEndStake = getCompoundedFrontEndStake(frontEnd);
        uint newFrontEndStake = compoundedFrontEndStake.sub(RUBCtoWithdraw);
        _updateFrontEndStakeAndSnapshots(frontEnd, newFrontEndStake);
        emit FrontEndStakeChanged(frontEnd, newFrontEndStake, msg.sender);

        _sendRUBCToDepositor(msg.sender, RUBCtoWithdraw);

        // Update deposit
        uint newDeposit = compoundedRUBCDeposit.sub(RUBCtoWithdraw);
        _updateDepositAndSnapshots(msg.sender, newDeposit);
        emit UserDepositChanged(msg.sender, newDeposit);

        emit ETHGainWithdrawn(msg.sender, depositorETHGain, RUBCLoss);  // RUBC Loss required for event log

        _sendETHGainToDepositor(depositorETHGain);
    }

    /* withdrawETHGainToTrove:
    * - Triggers a RBST issuance, based on time passed since the last issuance. The RBST issuance is shared between *all* depositors and front ends
    * - Sends all depositor's RBST gain to  depositor
    * - Sends all tagged front end's RBST gain to the tagged front end
    * - Transfers the depositor's entire ETH gain from the Stability Pool to the caller's trove
    * - Leaves their compounded deposit in the Stability Pool
    * - Updates snapshots for deposit and tagged front end stake */
    function withdrawETHGainToTrove(address _upperHint, address _lowerHint) external override {
        uint initialDeposit = deposits[msg.sender].initialValue;
        _requireUserHasDeposit(initialDeposit);
        _requireUserHasTrove(msg.sender);
        _requireUserHasETHGain(msg.sender);

        ICommunityIssuance communityIssuanceCached = communityIssuance;

        _triggerRBSTIssuance(communityIssuanceCached);

        uint depositorETHGain = getDepositorETHGain(msg.sender);

        uint compoundedRUBCDeposit = getCompoundedRUBCDeposit(msg.sender);
        uint RUBCLoss = initialDeposit.sub(compoundedRUBCDeposit); // Needed only for event log

        // First pay out any RBST gains
        address frontEnd = deposits[msg.sender].frontEndTag;
        _payOutRBSTGains(communityIssuanceCached, msg.sender, frontEnd);

        // Update front end stake
        uint compoundedFrontEndStake = getCompoundedFrontEndStake(frontEnd);
        uint newFrontEndStake = compoundedFrontEndStake;
        _updateFrontEndStakeAndSnapshots(frontEnd, newFrontEndStake);
        emit FrontEndStakeChanged(frontEnd, newFrontEndStake, msg.sender);

        _updateDepositAndSnapshots(msg.sender, compoundedRUBCDeposit);

        /* Emit events before transferring ETH gain to Trove.
         This lets the event log make more sense (i.e. so it appears that first the ETH gain is withdrawn
        and then it is deposited into the Trove, not the other way around). */
        emit ETHGainWithdrawn(msg.sender, depositorETHGain, RUBCLoss);
        emit UserDepositChanged(msg.sender, compoundedRUBCDeposit);

        ETH = ETH.sub(depositorETHGain);
        emit StabilityPoolETHBalanceUpdated(ETH);
        emit EtherSent(msg.sender, depositorETHGain);

        borrowerOperations.moveETHGainToTrove{ value: depositorETHGain }(msg.sender, _upperHint, _lowerHint);
    }

    // --- RBST issuance functions ---

    function _triggerRBSTIssuance(ICommunityIssuance _communityIssuance) internal {
        uint RBSTIssuance = _communityIssuance.issueRBST();
       _updateG(RBSTIssuance);
    }

    function _updateG(uint _RBSTIssuance) internal {
        uint totalRUBC = totalRUBCDeposits; // cached to save an SLOAD
        /*
        * When total deposits is 0, G is not updated. In this case, the RBST issued can not be obtained by later
        * depositors - it is missed out on, and remains in the balanceof the CommunityIssuance contract.
        *
        */
        if (totalRUBC == 0 || _RBSTIssuance == 0) {return;}

        uint RBSTPerUnitStaked;
        RBSTPerUnitStaked =_computeRBSTPerUnitStaked(_RBSTIssuance, totalRUBC);

        uint marginalRBSTGain = RBSTPerUnitStaked.mul(P);
        epochToScaleToG[currentEpoch][currentScale] = epochToScaleToG[currentEpoch][currentScale].add(marginalRBSTGain);

        emit G_Updated(epochToScaleToG[currentEpoch][currentScale], currentEpoch, currentScale);
    }

    function _computeRBSTPerUnitStaked(uint _RBSTIssuance, uint _totalRUBCDeposits) internal returns (uint) {
        /*  
        * Calculate the RBST-per-unit staked.  Division uses a "feedback" error correction, to keep the 
        * cumulative error low in the running total G:
        *
        * 1) Form a numerator which compensates for the floor division error that occurred the last time this 
        * function was called.  
        * 2) Calculate "per-unit-staked" ratio.
        * 3) Multiply the ratio back by its denominator, to reveal the current floor division error.
        * 4) Store this error for use in the next correction when this function is called.
        * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
        */
        uint RBSTNumerator = _RBSTIssuance.mul(DECIMAL_PRECISION).add(lastRBSTError);

        uint RBSTPerUnitStaked = RBSTNumerator.div(_totalRUBCDeposits);
        lastRBSTError = RBSTNumerator.sub(RBSTPerUnitStaked.mul(_totalRUBCDeposits));

        return RBSTPerUnitStaked;
    }

    // --- Liquidation functions ---

    /*
    * Cancels out the specified debt against the RUBC contained in the Stability Pool (as far as possible)
    * and transfers the Trove's ETH collateral from ActivePool to StabilityPool.
    * Only called by liquidation functions in the TroveManager.
    */
    function offset(uint _debtToOffset, uint _collToAdd) external override {
        _requireCallerIsTroveManager();
        uint totalRUBC = totalRUBCDeposits; // cached to save an SLOAD
        if (totalRUBC == 0 || _debtToOffset == 0) { return; }

        _triggerRBSTIssuance(communityIssuance);

        (uint ETHGainPerUnitStaked,
            uint RUBCLossPerUnitStaked) = _computeRewardsPerUnitStaked(_collToAdd, _debtToOffset, totalRUBC);

        _updateRewardSumAndProduct(ETHGainPerUnitStaked, RUBCLossPerUnitStaked);  // updates S and P

        _moveOffsetCollAndDebt(_collToAdd, _debtToOffset);
    }

    // --- Offset helper functions ---

    function _computeRewardsPerUnitStaked(
        uint _collToAdd,
        uint _debtToOffset,
        uint _totalRUBCDeposits
    )
        internal
        returns (uint ETHGainPerUnitStaked, uint RUBCLossPerUnitStaked)
    {
        /*
        * Compute the RUBC and ETH rewards. Uses a "feedback" error correction, to keep
        * the cumulative error in the P and S state variables low:
        *
        * 1) Form numerators which compensate for the floor division errors that occurred the last time this 
        * function was called.  
        * 2) Calculate "per-unit-staked" ratios.
        * 3) Multiply each ratio back by its denominator, to reveal the current floor division error.
        * 4) Store these errors for use in the next correction when this function is called.
        * 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
        */
        uint ETHNumerator = _collToAdd.mul(DECIMAL_PRECISION).add(lastETHError_Offset);

        assert(_debtToOffset <= _totalRUBCDeposits);
        if (_debtToOffset == _totalRUBCDeposits) {
            RUBCLossPerUnitStaked = DECIMAL_PRECISION;  // When the Pool depletes to 0, so does each deposit 
            lastRUBCLossError_Offset = 0;
        } else {
            uint RUBCLossNumerator = _debtToOffset.mul(DECIMAL_PRECISION).sub(lastRUBCLossError_Offset);
            /*
            * Add 1 to make error in quotient positive. We want "slightly too much" RUBC loss,
            * which ensures the error in any given compoundedRUBCDeposit favors the Stability Pool.
            */
            RUBCLossPerUnitStaked = (RUBCLossNumerator.div(_totalRUBCDeposits)).add(1);
            lastRUBCLossError_Offset = (RUBCLossPerUnitStaked.mul(_totalRUBCDeposits)).sub(RUBCLossNumerator);
        }

        ETHGainPerUnitStaked = ETHNumerator.div(_totalRUBCDeposits);
        lastETHError_Offset = ETHNumerator.sub(ETHGainPerUnitStaked.mul(_totalRUBCDeposits));

        return (ETHGainPerUnitStaked, RUBCLossPerUnitStaked);
    }

    // Update the Stability Pool reward sum S and product P
    function _updateRewardSumAndProduct(uint _ETHGainPerUnitStaked, uint _RUBCLossPerUnitStaked) internal {
        uint currentP = P;
        uint newP;

        assert(_RUBCLossPerUnitStaked <= DECIMAL_PRECISION);
        /*
        * The newProductFactor is the factor by which to change all deposits, due to the depletion of Stability Pool RUBC in the liquidation.
        * We make the product factor 0 if there was a pool-emptying. Otherwise, it is (1 - RUBCLossPerUnitStaked)
        */
        uint newProductFactor = uint(DECIMAL_PRECISION).sub(_RUBCLossPerUnitStaked);

        uint128 currentScaleCached = currentScale;
        uint128 currentEpochCached = currentEpoch;
        uint currentS = epochToScaleToSum[currentEpochCached][currentScaleCached];

        /*
        * Calculate the new S first, before we update P.
        * The ETH gain for any given depositor from a liquidation depends on the value of their deposit
        * (and the value of totalDeposits) prior to the Stability being depleted by the debt in the liquidation.
        *
        * Since S corresponds to ETH gain, and P to deposit loss, we update S first.
        */
        uint marginalETHGain = _ETHGainPerUnitStaked.mul(currentP);
        uint newS = currentS.add(marginalETHGain);
        epochToScaleToSum[currentEpochCached][currentScaleCached] = newS;
        emit S_Updated(newS, currentEpochCached, currentScaleCached);

        // If the Stability Pool was emptied, increment the epoch, and reset the scale and product P
        if (newProductFactor == 0) {
            currentEpoch = currentEpochCached.add(1);
            emit EpochUpdated(currentEpoch);
            currentScale = 0;
            emit ScaleUpdated(currentScale);
            newP = DECIMAL_PRECISION;

        // If multiplying P by a non-zero product factor would reduce P below the scale boundary, increment the scale
        } else if (currentP.mul(newProductFactor).div(DECIMAL_PRECISION) < SCALE_FACTOR) {
            newP = currentP.mul(newProductFactor).mul(SCALE_FACTOR).div(DECIMAL_PRECISION); 
            currentScale = currentScaleCached.add(1);
            emit ScaleUpdated(currentScale);
        } else {
            newP = currentP.mul(newProductFactor).div(DECIMAL_PRECISION);
        }

        assert(newP > 0);
        P = newP;

        emit P_Updated(newP);
    }

    function _moveOffsetCollAndDebt(uint _collToAdd, uint _debtToOffset) internal {
        IActivePool activePoolCached = activePool;

        // Cancel the liquidated RUBC debt with the RUBC in the stability pool
        activePoolCached.decreaseRUBCDebt(_debtToOffset);
        _decreaseRUBC(_debtToOffset);

        // Burn the debt that was successfully offset
        rubc.burn(address(this), _debtToOffset);

        activePoolCached.sendETH(address(this), _collToAdd);
    }

    function _decreaseRUBC(uint _amount) internal {
        uint newTotalRUBCDeposits = totalRUBCDeposits.sub(_amount);
        totalRUBCDeposits = newTotalRUBCDeposits;
        emit StabilityPoolRUBCBalanceUpdated(newTotalRUBCDeposits);
    }

    // --- Reward calculator functions for depositor and front end ---

    /* Calculates the ETH gain earned by the deposit since its last snapshots were taken.
    * Given by the formula:  E = d0 * (S - S(0))/P(0)
    * where S(0) and P(0) are the depositor's snapshots of the sum S and product P, respectively.
    * d0 is the last recorded deposit value.
    */
    function getDepositorETHGain(address _depositor) public view override returns (uint) {
        uint initialDeposit = deposits[_depositor].initialValue;

        if (initialDeposit == 0) { return 0; }

        Snapshots memory snapshots = depositSnapshots[_depositor];

        uint ETHGain = _getETHGainFromSnapshots(initialDeposit, snapshots);
        return ETHGain;
    }

    function _getETHGainFromSnapshots(uint initialDeposit, Snapshots memory snapshots) internal view returns (uint) {
        /*
        * Grab the sum 'S' from the epoch at which the stake was made. The ETH gain may span up to one scale change.
        * If it does, the second portion of the ETH gain is scaled by 1e9.
        * If the gain spans no scale change, the second portion will be 0.
        */
        uint128 epochSnapshot = snapshots.epoch;
        uint128 scaleSnapshot = snapshots.scale;
        uint S_Snapshot = snapshots.S;
        uint P_Snapshot = snapshots.P;

        uint firstPortion = epochToScaleToSum[epochSnapshot][scaleSnapshot].sub(S_Snapshot);
        uint secondPortion = epochToScaleToSum[epochSnapshot][scaleSnapshot.add(1)].div(SCALE_FACTOR);

        uint ETHGain = initialDeposit.mul(firstPortion.add(secondPortion)).div(P_Snapshot).div(DECIMAL_PRECISION);

        return ETHGain;
    }

    /*
    * Calculate the RBST gain earned by a deposit since its last snapshots were taken.
    * Given by the formula:  RBST = d0 * (G - G(0))/P(0)
    * where G(0) and P(0) are the depositor's snapshots of the sum G and product P, respectively.
    * d0 is the last recorded deposit value.
    */
    function getDepositorRBSTGain(address _depositor) public view override returns (uint) {
        uint initialDeposit = deposits[_depositor].initialValue;
        if (initialDeposit == 0) {return 0;}

        address frontEndTag = deposits[_depositor].frontEndTag;

        /*
        * If not tagged with a front end, the depositor gets a 100% cut of what their deposit earned.
        * Otherwise, their cut of the deposit's earnings is equal to the kickbackRate, set by the front end through
        * which they made their deposit.
        */
        uint kickbackRate = frontEndTag == address(0) ? DECIMAL_PRECISION : frontEnds[frontEndTag].kickbackRate;

        Snapshots memory snapshots = depositSnapshots[_depositor];

        uint RBSTGain = kickbackRate.mul(_getRBSTGainFromSnapshots(initialDeposit, snapshots)).div(DECIMAL_PRECISION);

        return RBSTGain;
    }