// Contract: StabilityPool
// Target function: function getDepositorETHGain(address _depositor) public view override returns (uint)
Address);
        checkContract(_activePoolAddress);
        checkContract(_rubcAddress);
        checkContract(_sortedTrovesAddress);
        checkContract(_priceFeedAddress);
        checkContract(_communityIssuanceAddress);

        borrowerOperations = IBorrowerOperations(_borrowerOperationsAddress);
        troveManager = ITroveManager(_troveManagerAddress);
        activePool = IActivePool(_activePoolAddress);
        rubc = IRUBC(_rubcAddress);
        sortedTroves = ISortedTroves(_sortedTrovesAddress);
        priceFeed = IPriceFeed(_priceFeedAddress);
        communityIssuance = ICommunityIssuance(_communityIssuanceAddress);

        emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress);
        emit TroveManagerAddressChanged(_troveManagerAddress);
        emit ActivePoolAddressChanged(_activePoolAddress);
        emit RUBCAddressChanged(_rubcAddress);
        emit SortedTrovesAddressChanged(_sortedTrovesAddress);
        emit PriceFeedAddressChanged(_priceFeedAddress);
        emit CommunityIssuanceAddressChanged(_communityIssuanceAddress);

        _renounceOwnership();
    }

    // --- Getters for public variables. Required by IPool interface ---

    function getETH() external view override returns (uint) {
        return ETH;
    }

    function getTotalRUBCDeposits() external view override returns (uint) {
        return totalRUBCDeposits;
    }

    // --- External Depositor Functions ---

    /*  provideToSP():
    *
    * - Triggers a RBST issuance, based on time passed since the last issuance. The RBST issuance is shared between *all* depositors and front ends
    * - Tags the deposit with the provided front end tag param, if it's a new deposit
    * - Sends depositor's accumulated gains (RBST, ETH) to depositor
    * - Sends the tagged front end's accumulated RBST gains to the tagged front end
    * - Increases deposit and tagged front end's stake, and takes new snapshots for each.
    */
    function provideToSP(uint _amount, address _frontEndTag) external override {
        _requireFrontEndIsRegisteredOrZero(_frontEndTag);
        _requireFrontEndNotRegistered(msg.sender);
        _requireNonZeroAmount(_amount);

        uint initialDeposit = deposits[msg.sender].initialValue;

        ICommunityIssuance communityIssuanceCached = communityIssuance;

        _triggerRBSTIssuance(communityIssuanceCached);

        if (initialDeposit == 0) {_setFrontEndTag(msg.sender, _frontEndTag);}
        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.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 ---