Sepolia Testnet

Contract

0x1a951f87347bD16a7aD2B44add9B4522E6452af6

Overview

ETH Balance

0 ETH

Multichain Info

N/A
Transaction Hash
Method
Block
From
To
Value

There are no matching entries

Please try again later

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block From To Value
54662162024-03-11 21:48:4877 days ago1710193728  Contract Creation0 ETH
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x27B2548F...94Ab2c30f
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
MultiCdpGetter

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 29 : MultiCdpGetter.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./CdpManager.sol";
import "./SortedCdps.sol";

/*  Helper contract for grabbing Cdp data for the front end. Not part of the core Ebtc system. */
contract MultiCdpGetter {
    struct CombinedCdpData {
        bytes32 id;
        uint256 debt;
        uint256 coll;
        uint256 stake;
        uint256 snapshotEBTCDebt;
    }

    CdpManager public immutable cdpManager;
    ISortedCdps public immutable sortedCdps;

    /// @notice Creates a new MultiCdpGetter contract
    /// @param _cdpManager The CdpManager contract
    /// @param _sortedCdps The ISortedCdps contract
    constructor(CdpManager _cdpManager, ISortedCdps _sortedCdps) {
        cdpManager = _cdpManager;
        sortedCdps = _sortedCdps;
    }

    /// @notice Retrieves multiple sorted Cdps
    /// @param _startIdx The start index for the linked list. The sign determines whether to start from the head or tail of the list.
    /// @dev Positive values start from the _head_ of the list and walk towards the _tail_, negative values start from the _tail_ of the list and walk towards the _head_
    /// @param _count The count of Cdps to retrieve. If the requested count exceeds the number of available Cdps starting from the _startIdx, the function will only retrieve the available Cdps.
    /// @return _cdps An array of CombinedCdpData structs
    function getMultipleSortedCdps(
        int _startIdx,
        uint256 _count
    ) external view returns (CombinedCdpData[] memory _cdps) {
        uint256 startIdx;
        bool descend;

        if (_startIdx >= 0) {
            startIdx = uint256(_startIdx);
            descend = true;
        } else {
            startIdx = uint256(-(_startIdx + 1));
            descend = false;
        }

        uint256 sortedCdpsSize = sortedCdps.getSize();

        if (startIdx >= sortedCdpsSize) {
            _cdps = new CombinedCdpData[](0);
        } else {
            uint256 maxCount = sortedCdpsSize - startIdx;

            if (_count > maxCount) {
                _count = maxCount;
            }

            if (descend) {
                _cdps = _getMultipleSortedCdpsFromHead(startIdx, _count);
            } else {
                _cdps = _getMultipleSortedCdpsFromTail(startIdx, _count);
            }
        }
    }

    /// @notice Internal function to retrieve multiple sorted Cdps from head
    /// @param _startIdx The start index
    /// @param _count The count of Cdps to retrieve
    /// @return _cdps An array of CombinedCdpData structs
    function _getMultipleSortedCdpsFromHead(
        uint256 _startIdx,
        uint256 _count
    ) internal view returns (CombinedCdpData[] memory _cdps) {
        bytes32 currentCdpId = sortedCdps.getFirst();

        for (uint256 idx = 0; idx < _startIdx; ++idx) {
            currentCdpId = sortedCdps.getNext(currentCdpId);
        }

        _cdps = new CombinedCdpData[](_count);

        for (uint256 idx = 0; idx < _count; ++idx) {
            _cdps[idx].id = currentCdpId;
            (, , _cdps[idx].stake, , ) = cdpManager.Cdps(currentCdpId);

            (_cdps[idx].debt, _cdps[idx].coll) = cdpManager.getSyncedDebtAndCollShares(currentCdpId);
            (_cdps[idx].snapshotEBTCDebt) = cdpManager.cdpDebtRedistributionIndex(currentCdpId);

            currentCdpId = sortedCdps.getNext(currentCdpId);
        }
    }

    /// @notice Internal function to retrieve multiple sorted Cdps from tail
    /// @param _startIdx The start index
    /// @param _count The count of Cdps to retrieve
    /// @return _cdps An array of CombinedCdpData structs
    function _getMultipleSortedCdpsFromTail(
        uint256 _startIdx,
        uint256 _count
    ) internal view returns (CombinedCdpData[] memory _cdps) {
        bytes32 currentCdpId = sortedCdps.getLast();

        for (uint256 idx = 0; idx < _startIdx; ++idx) {
            currentCdpId = sortedCdps.getPrev(currentCdpId);
        }

        _cdps = new CombinedCdpData[](_count);

        for (uint256 idx = 0; idx < _count; ++idx) {
            _cdps[idx].id = currentCdpId;
            (, , _cdps[idx].stake, , ) = cdpManager.Cdps(currentCdpId);

            (_cdps[idx].debt, _cdps[idx].coll) = cdpManager.getSyncedDebtAndCollShares(currentCdpId);
            (_cdps[idx].snapshotEBTCDebt) = cdpManager.cdpDebtRedistributionIndex(currentCdpId);

            currentCdpId = sortedCdps.getPrev(currentCdpId);
        }
    }
}

File 2 of 29 : CdpManager.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./Interfaces/ICdpManager.sol";
import "./Interfaces/ICollSurplusPool.sol";
import "./Interfaces/IEBTCToken.sol";
import "./Interfaces/ISortedCdps.sol";
import "./Dependencies/ICollateralTokenOracle.sol";
import "./CdpManagerStorage.sol";
import "./Dependencies/Proxy.sol";
import "./Dependencies/EbtcBase.sol";
import "./Dependencies/EbtcMath.sol";

/// @title CdpManager is mainly in charge of all Cdp related core processing like collateral & debt accounting, split fee calculation, redemption, etc
/// @notice Except for redemption, end user typically will interact with BorrowerOeprations for individual Cdp actions
/// @dev CdpManager also handles liquidation through delegatecall to LiquidationLibrary
contract CdpManager is CdpManagerStorage, ICdpManager, Proxy {
    // --- Dependency setter ---

    /// @notice Constructor for CdpManager contract.
    /// @dev Sets up dependencies and initial staking reward split.
    /// @param _liquidationLibraryAddress Address of the liquidation library.
    /// @param _authorityAddress Address of the authority.
    /// @param _borrowerOperationsAddress Address of BorrowerOperations.
    /// @param _collSurplusPoolAddress Address of CollSurplusPool.
    /// @param _ebtcTokenAddress Address of the eBTC token.
    /// @param _sortedCdpsAddress Address of the SortedCDPs.
    /// @param _activePoolAddress Address of the ActivePool.
    /// @param _priceFeedAddress Address of the price feed.
    /// @param _collTokenAddress Address of the collateral token.
    constructor(
        address _liquidationLibraryAddress,
        address _authorityAddress,
        address _borrowerOperationsAddress,
        address _collSurplusPoolAddress,
        address _ebtcTokenAddress,
        address _sortedCdpsAddress,
        address _activePoolAddress,
        address _priceFeedAddress,
        address _collTokenAddress
    )
        CdpManagerStorage(
            _liquidationLibraryAddress,
            _authorityAddress,
            _borrowerOperationsAddress,
            _collSurplusPoolAddress,
            _ebtcTokenAddress,
            _sortedCdpsAddress,
            _activePoolAddress,
            _priceFeedAddress,
            _collTokenAddress
        )
    {
        stakingRewardSplit = STAKING_REWARD_SPLIT;
        // Emit initial value for analytics
        emit StakingRewardSplitSet(stakingRewardSplit);

        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
        _syncStEthIndex(_oldIndex, _newIndex);
        systemStEthFeePerUnitIndex = DECIMAL_PRECISION;
    }

    // --- Cdp Liquidation functions ---
    // -----------------------------------------------------------------
    //    Cdp ICR     |       Liquidation Behavior (TODO gas compensation?)
    //
    //  < MCR         |  debt could be fully repaid by liquidator
    //                |  and ALL collateral transferred to liquidator
    //                |  OR debt could be partially repaid by liquidator and
    //                |  liquidator could get collateral of (repaidDebt * max(LICR, min(ICR, MCR)) / price)
    //
    //  > MCR & < TCR |  only liquidatable in Recovery Mode (TCR < CCR)
    //                |  debt could be fully repaid by liquidator
    //                |  and up to (repaid debt * MCR) worth of collateral
    //                |  transferred to liquidator while the residue of collateral
    //                |  will be available in CollSurplusPool for owner to claim
    //                |  OR debt could be partially repaid by liquidator and
    //                |  liquidator could get collateral of (repaidDebt * max(LICR, min(ICR, MCR)) / price)
    // -----------------------------------------------------------------

    /// @notice Fully liquidate a single Cdp by ID. Cdp must meet the criteria for liquidation at the time of execution.
    /// @notice callable by anyone, attempts to liquidate the CdpId. Executes successfully if Cdp meets the conditions for liquidation (e.g. in Normal Mode, it liquidates if the Cdp's ICR < the system MCR).
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
    /// @param _cdpId ID of the Cdp to liquidate.
    function liquidate(bytes32 _cdpId) external override {
        _delegate(liquidationLibrary);
    }

    /// @notice Partially liquidate a single Cdp.
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
    /// @param _cdpId ID of the Cdp to partially liquidate.
    /// @param _partialAmount Amount to partially liquidate.
    /// @param _upperPartialHint Upper hint for reinsertion of the Cdp into the linked list.
    /// @param _lowerPartialHint Lower hint for reinsertion of the Cdp into the linked list.
    function partiallyLiquidate(
        bytes32 _cdpId,
        uint256 _partialAmount,
        bytes32 _upperPartialHint,
        bytes32 _lowerPartialHint
    ) external override {
        _requireAmountGreaterThanMin(_partialAmount);
        _delegate(liquidationLibrary);
    }

    // --- Batch/Sequence liquidation functions ---

    /// @notice Attempt to liquidate a custom list of Cdps provided by the caller
    /// @notice Callable by anyone, accepts a custom list of Cdps addresses as an argument.
    /// @notice Steps through the provided list and attempts to liquidate every Cdp, until it reaches the end or it runs out of gas.
    /// @notice A Cdp is liquidated only if it meets the conditions for liquidation.
    /// @dev forwards msg.data directly to the liquidation library using OZ proxy core delegation function
    /// @param _cdpArray Array of Cdps to liquidate.
    function batchLiquidateCdps(bytes32[] memory _cdpArray) external override {
        _delegate(liquidationLibrary);
    }

    // --- Redemption functions ---

    /// @notice // Redeem as much collateral as possible from given Cdp in exchange for EBTC up to specified maximum
    /// @param _redeemColFromCdp Struct containing variables for redeeming collateral.
    /// @return singleRedemption Struct containing redemption values.
    function _redeemCollateralFromCdp(
        SingleRedemptionInputs memory _redeemColFromCdp
    ) internal returns (SingleRedemptionValues memory singleRedemption) {
        // Determine the remaining amount (lot) to be redeemed,
        // capped by the entire debt of the Cdp minus the liquidation reserve
        singleRedemption.debtToRedeem = EbtcMath._min(
            _redeemColFromCdp.maxEBTCamount,
            Cdps[_redeemColFromCdp.cdpId].debt /// @audit Redeem everything
        );

        singleRedemption.collSharesDrawn = collateral.getSharesByPooledEth(
            (singleRedemption.debtToRedeem * DECIMAL_PRECISION) / _redeemColFromCdp.price
        );

        // Repurposing this struct here to avoid stack too deep.
        CdpDebtAndCollShares memory _oldDebtAndColl = CdpDebtAndCollShares(
            Cdps[_redeemColFromCdp.cdpId].debt,
            Cdps[_redeemColFromCdp.cdpId].coll
        );

        // Decrease the debt and collateral of the current Cdp according to the EBTC lot and corresponding ETH to send
        uint256 newDebt = _oldDebtAndColl.debt - singleRedemption.debtToRedeem;
        uint256 newColl = _oldDebtAndColl.collShares - singleRedemption.collSharesDrawn;

        if (newDebt == 0) {
            // No debt remains, close Cdp
            // No debt left in the Cdp, therefore the cdp gets closed
            {
                address _borrower = sortedCdps.getOwnerAddress(_redeemColFromCdp.cdpId);
                uint256 _liquidatorRewardShares = uint256(
                    Cdps[_redeemColFromCdp.cdpId].liquidatorRewardShares
                );

                singleRedemption.collSurplus = newColl; // Collateral surplus processed on full redemption
                singleRedemption.liquidatorRewardShares = _liquidatorRewardShares;
                singleRedemption.fullRedemption = true;

                _closeCdpByRedemption(
                    _redeemColFromCdp.cdpId,
                    0,
                    newColl,
                    _liquidatorRewardShares,
                    _borrower
                );

                emit CdpUpdated(
                    _redeemColFromCdp.cdpId,
                    _borrower,
                    msg.sender,
                    _oldDebtAndColl.debt,
                    _oldDebtAndColl.collShares,
                    0,
                    0,
                    0,
                    CdpOperation.redeemCollateral
                );
            }
        } else {
            // Debt remains, reinsert Cdp
            uint256 newNICR = EbtcMath._computeNominalCR(newColl, newDebt);

            /*
             * If the provided hint is out of date, we bail since trying to reinsert without a good hint will almost
             * certainly result in running out of gas.
             *
             * If the resultant net coll of the partial is less than the minimum, we bail.
             */
            if (
                newNICR != _redeemColFromCdp.partialRedemptionHintNICR ||
                collateral.getPooledEthByShares(newColl) < MIN_NET_STETH_BALANCE ||
                newDebt < MIN_CHANGE
            ) {
                _updateStakeAndTotalStakes(_redeemColFromCdp.cdpId);

                emit CdpUpdated(
                    _redeemColFromCdp.cdpId,
                    ISortedCdps(sortedCdps).getOwnerAddress(_redeemColFromCdp.cdpId),
                    msg.sender,
                    _oldDebtAndColl.debt,
                    _oldDebtAndColl.collShares,
                    _oldDebtAndColl.debt,
                    _oldDebtAndColl.collShares,
                    Cdps[_redeemColFromCdp.cdpId].stake,
                    CdpOperation.failedPartialRedemption
                );

                singleRedemption.cancelledPartial = true;
                return singleRedemption;
            }

            singleRedemption.newPartialNICR = newNICR;

            Cdps[_redeemColFromCdp.cdpId].debt = newDebt;
            Cdps[_redeemColFromCdp.cdpId].coll = newColl;
            _updateStakeAndTotalStakes(_redeemColFromCdp.cdpId);

            emit CdpUpdated(
                _redeemColFromCdp.cdpId,
                ISortedCdps(sortedCdps).getOwnerAddress(_redeemColFromCdp.cdpId),
                msg.sender,
                _oldDebtAndColl.debt,
                _oldDebtAndColl.collShares,
                newDebt,
                newColl,
                Cdps[_redeemColFromCdp.cdpId].stake,
                CdpOperation.redeemCollateral
            );
        }

        return singleRedemption;
    }

    /*
     * Called when a full redemption occurs, and closes the cdp.
     * The redeemer swaps (debt) EBTC for (debt)
     * worth of stETH, so the stETH liquidation reserve is all that remains.
     * In order to close the cdp, the stETH liquidation reserve is returned to the Cdp owner,
     * The debt recorded on the cdp's struct is zero'd elswhere, in _closeCdp.
     * Any surplus stETH left in the cdp, is sent to the Coll surplus pool, and can be later claimed by the borrower.
     */
    function _closeCdpByRedemption(
        bytes32 _cdpId,
        uint256 _EBTC,
        uint256 _collSurplus,
        uint256 _liquidatorRewardShares,
        address _borrower
    ) internal {
        _closeCdpWithoutRemovingSortedCdps(_cdpId, Status.closedByRedemption);

        // Update Active Pool EBTC, and send ETH to account
        activePool.decreaseSystemDebt(_EBTC);

        // Register stETH surplus from upcoming transfers of stETH collateral and liquidator reward shares
        collSurplusPool.increaseSurplusCollShares(
            _cdpId,
            _borrower,
            _collSurplus,
            _liquidatorRewardShares
        );

        // CEI: send stETH coll and liquidator reward shares from Active Pool to CollSurplus Pool
        activePool.transferSystemCollSharesAndLiquidatorReward(
            address(collSurplusPool),
            _collSurplus,
            _liquidatorRewardShares
        );
    }

    /// @notice Returns true if the CdpId specified is the lowest-ICR Cdp in the linked list that still has MCR > ICR
    /// @dev Returns false if the specified CdpId hint is blank
    /// @dev Returns false if the specified CdpId hint doesn't exist in the list
    /// @dev Returns false if the ICR of the specified CdpId is < MCR
    /// @dev Returns true if the specified CdpId is not blank, exists in the list, has an ICR > MCR, and the next lower Cdp in the list is either blank or has an ICR < MCR.
    function _isValidFirstRedemptionHint(
        bytes32 _firstRedemptionHint,
        uint256 _price
    ) internal view returns (bool) {
        if (
            _firstRedemptionHint == sortedCdps.nonExistId() ||
            !sortedCdps.contains(_firstRedemptionHint) ||
            getSyncedICR(_firstRedemptionHint, _price) < MCR
        ) {
            return false;
        }

        bytes32 nextCdp = sortedCdps.getNext(_firstRedemptionHint);
        return nextCdp == sortedCdps.nonExistId() || getSyncedICR(nextCdp, _price) < MCR;
    }

    /// @notice Send _debt EBTC to the system and redeem the corresponding amount of collateral
    /// @notice from as many Cdps as are needed to fill the redemption request.
    /// @notice
    /// @notice Note that if _debt is very large, this function can run out of gas, specially if traversed cdps are small (meaning many small Cdps are redeemed against).
    /// @notice This can be easily avoided by splitting the total _debt in appropriate chunks and calling the function multiple times.
    /// @notice
    /// @notice There is a optional parameter `_maxIterations` which can also be provided, so the loop through Cdps is capped (if it’s zero, it will be ignored).
    /// @notice This makes it easier to avoid OOG for the frontend, as only knowing approximately the average cost of an iteration is enough,
    /// @notice without needing to know the "topology" of the cdp list. It also avoids the need to set the cap in stone in the contract,
    /// @notice nor doing gas calculations, as both gas price and opcode costs can vary.
    /// @notice
    /// @notice All Cdps that are redeemed from -- with the likely exception of the last one -- will end up with no debt left,
    /// @notice therefore they will be closed.
    /// @notice If the last Cdp does have some remaining debt & collateral (it has a valid meaningful ICR) then reinsertion of the CDP
    /// @notice could be anywhere in the entire SortedCdps list, therefore this redemption requires a hint.
    /// @notice
    /// @notice A frontend should use HintHelper.getRedemptionHints() to calculate what the ICR of this Cdp will be after redemption,
    /// @notice and pass a hint for its position in the SortedCdps list along with the ICR value that the hint was found for.
    /// @notice
    /// @notice If another transaction modifies the list between calling getRedemptionHints()
    /// @notice and passing the hints to redeemCollateral(), it is very likely that the last (partially)
    /// @notice redeemed Cdp would end up with a different ICR than what the hint is for.
    /// @notice
    /// @notice In this case, the redemption will stop after the last completely redeemed Cdp and the sender will keep the
    /// @notice remaining EBTC amount, which they can attempt to redeem later.
    /// @param _debt The total eBTC debt amount to be redeemed
    /// @param _firstRedemptionHint The first CdpId to be considered for redemption, could get from HintHelper.getRedemptionHints()
    /// @param _upperPartialRedemptionHint The first CdpId to be considered for redemption, could get from HintHelper.getApproxHint(_partialRedemptionHintNICR) then SortedCdps.findInsertPosition(_partialRedemptionHintNICR)
    /// @param _lowerPartialRedemptionHint The first CdpId to be considered for redemption, could get from HintHelper.getApproxHint(_partialRedemptionHintNICR) then SortedCdps.findInsertPosition(_partialRedemptionHintNICR)
    /// @param _partialRedemptionHintNICR The new Nominal Collateral Ratio (NICR) of the last redeemed CDP after partial redemption, could get from HintHelper.getRedemptionHints()
    /// @param _maxIterations The maximum allowed iteration along the SortedCdps loop, if zero then there is no limit
    /// @param _maxFeePercentage The maximum allowed redemption fee for this redemption
    function redeemCollateral(
        uint256 _debt,
        bytes32 _firstRedemptionHint,
        bytes32 _upperPartialRedemptionHint,
        bytes32 _lowerPartialRedemptionHint,
        uint256 _partialRedemptionHintNICR,
        uint256 _maxIterations,
        uint256 _maxFeePercentage
    ) external override nonReentrantSelfAndBOps {
        RedemptionTotals memory totals;

        // early check to ensure redemption is not paused
        require(redemptionsPaused == false, "CdpManager: Redemptions Paused");

        _requireValidMaxFeePercentage(_maxFeePercentage);

        _syncGlobalAccounting(); // Apply state, we will syncGracePeriod at end of function

        totals.price = priceFeed.fetchPrice();
        {
            (
                uint256 tcrAtStart,
                uint256 systemCollSharesAtStart,
                uint256 systemDebtAtStart
            ) = _getTCRWithSystemDebtAndCollShares(totals.price);
            totals.tcrAtStart = tcrAtStart;
            totals.systemCollSharesAtStart = systemCollSharesAtStart;
            totals.systemDebtAtStart = systemDebtAtStart;

            if (!activePool.twapDisabled()) {
                try activePool.observe() returns (uint256 _twapSystemDebtAtStart) {
                    // @audit Return the smaller value of the two, bias towards a larger redemption scaling fee
                    totals.twapSystemDebtAtStart = EbtcMath._min(
                        _twapSystemDebtAtStart,
                        systemDebtAtStart
                    );
                } catch {
                    totals.twapSystemDebtAtStart = systemDebtAtStart;
                }
            } else {
                totals.twapSystemDebtAtStart = systemDebtAtStart;
            }
        }

        _requireTCRisNotBelowMCR(totals.price, totals.tcrAtStart);
        _requireAmountGreaterThanMin(_debt);

        _requireEbtcBalanceCoversRedemptionAndWithinSupply(
            msg.sender,
            _debt,
            totals.systemDebtAtStart
        );

        totals.remainingDebtToRedeem = _debt;
        address currentBorrower;
        bytes32 _cId = _firstRedemptionHint;

        if (_isValidFirstRedemptionHint(_firstRedemptionHint, totals.price)) {
            currentBorrower = sortedCdps.getOwnerAddress(_firstRedemptionHint);
        } else {
            _cId = sortedCdps.getLast();
            currentBorrower = sortedCdps.getOwnerAddress(_cId);
            // Find the first cdp with ICR >= MCR
            while (currentBorrower != address(0) && getSyncedICR(_cId, totals.price) < MCR) {
                _cId = sortedCdps.getPrev(_cId);
                currentBorrower = sortedCdps.getOwnerAddress(_cId);
            }
        }

        // Loop through the Cdps starting from the one with lowest collateral
        // ratio until _amount of EBTC is exchanged for collateral
        if (_maxIterations == 0) {
            _maxIterations = type(uint256).max;
        }

        bytes32 _firstRedeemed = _cId;
        bytes32 _lastRedeemed = _cId;
        uint256 _numCdpsFullyRedeemed;

        /**
            Core Redemption Loop
        */
        uint256 _partialRedeemedNewNICR;
        while (
            currentBorrower != address(0) && totals.remainingDebtToRedeem > 0 && _maxIterations > 0
        ) {
            // Save the address of the Cdp preceding the current one, before potentially modifying the list
            {
                _syncAccounting(_cId); /// @audit This happens even if the re-insertion doesn't

                SingleRedemptionInputs memory _redeemColFromCdp = SingleRedemptionInputs(
                    _cId,
                    totals.remainingDebtToRedeem,
                    totals.price,
                    _upperPartialRedemptionHint,
                    _lowerPartialRedemptionHint,
                    _partialRedemptionHintNICR
                );
                SingleRedemptionValues memory singleRedemption = _redeemCollateralFromCdp(
                    _redeemColFromCdp
                );
                // Partial redemption was cancelled (out-of-date hint, or new net debt < minimum),
                // therefore we could not redeem from the last Cdp
                if (singleRedemption.cancelledPartial) break;

                // prepare for reinsertion if there is partial redemption
                if (singleRedemption.newPartialNICR > 0) {
                    _partialRedeemedNewNICR = singleRedemption.newPartialNICR;
                }

                totals.debtToRedeem = totals.debtToRedeem + singleRedemption.debtToRedeem;
                totals.collSharesDrawn = totals.collSharesDrawn + singleRedemption.collSharesDrawn;
                totals.remainingDebtToRedeem =
                    totals.remainingDebtToRedeem -
                    singleRedemption.debtToRedeem;
                totals.totalCollSharesSurplus =
                    totals.totalCollSharesSurplus +
                    singleRedemption.collSurplus;

                bytes32 _nextId = sortedCdps.getPrev(_cId);
                if (singleRedemption.fullRedemption) {
                    _lastRedeemed = _cId;
                    _numCdpsFullyRedeemed = _numCdpsFullyRedeemed + 1;
                    _cId = _nextId;
                }

                address nextUserToCheck = sortedCdps.getOwnerAddress(_nextId);
                currentBorrower = nextUserToCheck;
            }
            _maxIterations--;
        }
        require(totals.collSharesDrawn > 0, "CdpManager: Unable to redeem any amount");

        // remove from sortedCdps
        if (_numCdpsFullyRedeemed == 1) {
            sortedCdps.remove(_firstRedeemed);
        } else if (_numCdpsFullyRedeemed > 1) {
            bytes32[] memory _toRemoveIds = _getCdpIdsToRemove(
                _lastRedeemed,
                _numCdpsFullyRedeemed,
                _firstRedeemed
            );
            sortedCdps.batchRemove(_toRemoveIds);
        }

        // reinsert partially redemeed CDP if any
        if (_cId != bytes32(0) && _partialRedeemedNewNICR > 0) {
            sortedCdps.reInsert(
                _cId,
                _partialRedeemedNewNICR,
                _upperPartialRedemptionHint,
                _lowerPartialRedemptionHint
            );
        }

        // Decay the baseRate due to time passed, and then increase it according to the size of this redemption.
        // Use the saved total EBTC supply value, from before it was reduced by the redemption.
        _updateBaseRateFromRedemption(
            totals.collSharesDrawn,
            totals.price,
            totals.twapSystemDebtAtStart
        );

        // Calculate the ETH fee
        totals.feeCollShares = _getRedemptionFee(totals.collSharesDrawn);

        _requireUserAcceptsFee(totals.feeCollShares, totals.collSharesDrawn, _maxFeePercentage);

        totals.collSharesToRedeemer = totals.collSharesDrawn - totals.feeCollShares;

        _syncGracePeriodForGivenValues(
            totals.systemCollSharesAtStart - totals.collSharesDrawn - totals.totalCollSharesSurplus,
            totals.systemDebtAtStart - totals.debtToRedeem,
            totals.price
        );

        emit Redemption(
            _debt,
            totals.debtToRedeem,
            totals.collSharesDrawn,
            totals.feeCollShares,
            msg.sender
        );

        // Burn the total eBTC that is redeemed
        ebtcToken.burn(msg.sender, totals.debtToRedeem);

        // Update Active Pool eBTC debt internal accounting
        activePool.decreaseSystemDebt(totals.debtToRedeem);

        // Allocate the stETH fee to the FeeRecipient
        activePool.allocateSystemCollSharesToFeeRecipient(totals.feeCollShares);

        // CEI: Send the stETH drawn to the redeemer
        activePool.transferSystemCollShares(msg.sender, totals.collSharesToRedeemer);

        // final check if we not in RecoveryMode at redemption start
        if (!_checkRecoveryModeForTCR(totals.tcrAtStart)) {
            require(
                !_checkRecoveryMode(totals.price),
                "CdpManager: redemption should not trigger RecoveryMode"
            );
        }
    }

    // --- Helper functions ---

    function _getCdpIdsToRemove(
        bytes32 _start,
        uint256 _total,
        bytes32 _end
    ) internal view returns (bytes32[] memory) {
        uint256 _cnt = _total;
        bytes32 _id = _start;
        bytes32[] memory _toRemoveIds = new bytes32[](_total);
        while (_cnt > 0 && _id != bytes32(0)) {
            _toRemoveIds[_total - _cnt] = _id;
            _cnt = _cnt - 1;
            _id = sortedCdps.getNext(_id);
        }
        require(_toRemoveIds[0] == _start, "CdpManager: batchRemoveSortedCdpIds check start error");
        require(
            _toRemoveIds[_total - 1] == _end,
            "CdpManager: batchRemoveSortedCdpIds check end error"
        );
        return _toRemoveIds;
    }

    /// @notice Synchorize the accounting for the specified Cdp
    /// @notice It will synchronize global accounting with stETH share index first
    /// @notice then apply split fee and debt redistribution if any
    /// @param _cdpId cdpId to sync pending accounting state for
    function syncAccounting(bytes32 _cdpId) external virtual override {
        /// @audit Opening can cause invalid reordering of Cdps due to changing values without reInserting into sortedCdps
        _requireCallerIsBorrowerOperations();
        return _syncAccounting(_cdpId);
    }

    /// @notice Update stake for the specified Cdp and total stake within the system.
    /// @dev Only BorrowerOperations is allowed to call this function
    /// @param _cdpId cdpId to update stake for
    function updateStakeAndTotalStakes(bytes32 _cdpId) external override returns (uint256) {
        _requireCallerIsBorrowerOperations();
        return _updateStakeAndTotalStakes(_cdpId);
    }

    /// @notice Close the specified Cdp by ID.
    /// @dev Only BorrowerOperations is allowed to call this function.
    /// @dev This will close the Cdp and update its status to `closedByOwner`
    /// @dev The collateral and debt will be zero'd out
    /// @dev The Cdp will be removed from the sorted list
    /// @dev The close will emit a `CdpUpdated` event containing closing details
    /// @param _cdpId ID of the Cdp to close
    /// @param _borrower Address of the Cdp borrower
    /// @param _debt The recorded Cdp debt prior to closing
    /// @param _coll The recorded Cdp collateral shares prior to closing
    function closeCdp(
        bytes32 _cdpId,
        address _borrower,
        uint256 _debt,
        uint256 _coll
    ) external override {
        _requireCallerIsBorrowerOperations();
        emit CdpUpdated(_cdpId, _borrower, msg.sender, _debt, _coll, 0, 0, 0, CdpOperation.closeCdp);
        return _closeCdp(_cdpId, Status.closedByOwner);
    }

    // --- Recovery Mode and TCR functions ---

    /// @notice Get the sum of debt units assigned to all Cdps within eBTC system
    /// @dev It is actually the `systemDebt` value of the ActivePool.
    /// @return entireSystemDebt entire system debt accounting value
    function getSystemDebt() public view returns (uint256 entireSystemDebt) {
        return _getSystemDebt();
    }

    /// @notice The total collateralization ratio (TCR) of the system as a cached "view" (maybe outdated)
    /// @dev It is based on the current recorded system debt and collateral.
    /// @dev Possible split fee is not considered with this function.
    /// @dev Please use getSyncedTCR() otherwise
    /// @param _price The current stETH:BTC price
    /// @return TCR The cached total collateralization ratio (TCR) of the system (does not take into account pending global state)
    function getCachedTCR(uint256 _price) external view override returns (uint256) {
        return _getCachedTCR(_price);
    }

    /// @notice Whether or not the system is in Recovery Mode (TCR is below the CCR)
    /// @dev Possible split fee is not considered with this function.
    /// @dev Please use getSyncedTCR() otherwise
    /// @param _price The current stETH:BTC price
    /// @return True if system is in recovery mode with cached values (TCR < CCR), false otherwise
    function checkRecoveryMode(uint256 _price) external view override returns (bool) {
        return _checkRecoveryMode(_price);
    }

    // Check whether or not the system *would be* in Recovery Mode,
    // given an ETH:USD price, and the entire system coll and debt.
    function _checkPotentialRecoveryMode(
        uint256 _systemCollShares,
        uint256 _systemDebt,
        uint256 _price
    ) internal view returns (bool) {
        uint256 TCR = _computeTCRWithGivenSystemValues(_systemCollShares, _systemDebt, _price);
        return TCR < CCR;
    }

    // --- Redemption fee functions ---

    /*
     * This function has two impacts on the baseRate state variable:
     * 1) decays the baseRate based on time passed since last redemption or EBTC borrowing operation.
     * then,
     * 2) increases the baseRate based on the amount redeemed, as a proportion of total supply
     */
    function _updateBaseRateFromRedemption(
        uint256 _ETHDrawn,
        uint256 _price,
        uint256 _totalEBTCSupply
    ) internal returns (uint256) {
        uint256 decayedBaseRate = _calcDecayedBaseRate();

        /* Convert the drawn ETH back to EBTC at face value rate (1 EBTC:1 USD), in order to get
         * the fraction of total supply that was redeemed at face value. */
        uint256 redeemedEBTCFraction = (collateral.getPooledEthByShares(_ETHDrawn) * _price) /
            _totalEBTCSupply;

        uint256 newBaseRate = decayedBaseRate + (redeemedEBTCFraction / beta);
        newBaseRate = EbtcMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100%
        require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption

        // Update the baseRate state variable
        baseRate = newBaseRate;
        emit BaseRateUpdated(newBaseRate);

        _updateLastRedemptionTimestamp();

        return newBaseRate;
    }

    /// @return current fee rate for redemption with base rate
    function getRedemptionRate() public view override returns (uint256) {
        return _calcRedemptionRate(baseRate);
    }

    /// @return current fee rate for redemption with decayed base rate
    function getRedemptionRateWithDecay() public view override returns (uint256) {
        return _calcRedemptionRate(_calcDecayedBaseRate());
    }

    function _calcRedemptionRate(uint256 _baseRate) internal view returns (uint256) {
        return
            EbtcMath._min(
                redemptionFeeFloor + _baseRate,
                DECIMAL_PRECISION // cap at a maximum of 100%
            );
    }

    function _getRedemptionFee(uint256 _ETHDrawn) internal view returns (uint256) {
        return _calcRedemptionFee(getRedemptionRate(), _ETHDrawn);
    }

    /// @return redemption fee for the specified collateral amount
    /// @param _stETHToRedeem The total expected stETH amount to redeem
    function getRedemptionFeeWithDecay(
        uint256 _stETHToRedeem
    ) external view override returns (uint256) {
        return _calcRedemptionFee(getRedemptionRateWithDecay(), _stETHToRedeem);
    }

    function _calcRedemptionFee(
        uint256 _redemptionRate,
        uint256 _ETHDrawn
    ) internal pure returns (uint256) {
        uint256 redemptionFee = (_redemptionRate * _ETHDrawn) / DECIMAL_PRECISION;
        require(redemptionFee < _ETHDrawn, "CdpManager: Fee would eat up all returned collateral");
        return redemptionFee;
    }

    function _decayBaseRate() internal {
        uint256 decayedBaseRate = _calcDecayedBaseRate();
        require(decayedBaseRate <= DECIMAL_PRECISION, "CdpManager: baseRate too large!"); // The baseRate can decay to 0

        baseRate = decayedBaseRate;
        emit BaseRateUpdated(decayedBaseRate);

        _updateLastRedemptionTimestamp();
    }

    // --- Internal fee functions ---

    // Update the last fee operation time only if time passed >= decay interval. This prevents base rate griefing.
    function _updateLastRedemptionTimestamp() internal {
        uint256 timePassed = block.timestamp > lastRedemptionTimestamp
            ? block.timestamp - lastRedemptionTimestamp
            : 0;

        if (timePassed >= SECONDS_IN_ONE_MINUTE) {
            // Using the effective elapsed time that is consumed so far to update lastRedemptionTimestamp
            // instead block.timestamp for consistency with _calcDecayedBaseRate()
            lastRedemptionTimestamp += _minutesPassedSinceLastRedemption() * SECONDS_IN_ONE_MINUTE;
            emit LastRedemptionTimestampUpdated(block.timestamp);
        }
    }

    function _calcDecayedBaseRate() internal view returns (uint256) {
        uint256 minutesPassed = _minutesPassedSinceLastRedemption();
        uint256 decayFactor = EbtcMath._decPow(minuteDecayFactor, minutesPassed);

        return (baseRate * decayFactor) / DECIMAL_PRECISION;
    }

    function _minutesPassedSinceLastRedemption() internal view returns (uint256) {
        return
            block.timestamp > lastRedemptionTimestamp
                ? ((block.timestamp - lastRedemptionTimestamp) / SECONDS_IN_ONE_MINUTE)
                : 0;
    }

    /// @return timestamp when this contract is deployed
    function getDeploymentStartTime() public view returns (uint256) {
        return deploymentStartTime;
    }

    /// @notice Check whether or not the system *would be* in Recovery Mode,
    /// @notice given an ETH:eBTC price, and the entire system coll and debt.
    /// @param _systemCollShares The total collateral of the system to be used for the TCR calculation
    /// @param _systemDebt The total debt of the system to be used for the TCR calculation
    /// @param _price The ETH:eBTC price to be used for the TCR calculation
    /// @return flag (true or false) whether the system would be in Recovery Mode for specified status parameters
    function checkPotentialRecoveryMode(
        uint256 _systemCollShares,
        uint256 _systemDebt,
        uint256 _price
    ) external view returns (bool) {
        return _checkPotentialRecoveryMode(_systemCollShares, _systemDebt, _price);
    }

    // --- 'require' wrapper functions ---

    function _requireEbtcBalanceCoversRedemptionAndWithinSupply(
        address _redeemer,
        uint256 _amount,
        uint256 _totalSupply
    ) internal view {
        uint256 callerBalance = ebtcToken.balanceOf(_redeemer);
        require(
            callerBalance >= _amount,
            "CdpManager: Requested redemption amount must be <= user's EBTC token balance"
        );
        require(
            callerBalance <= _totalSupply,
            "CdpManager: redeemer's EBTC balance exceeds total supply!"
        );
    }

    function _requireAmountGreaterThanMin(uint256 _amount) internal pure {
        require(_amount >= MIN_CHANGE, "CdpManager: Amount must be greater than min");
    }

    function _requireTCRisNotBelowMCR(uint256 _price, uint256 _TCR) internal view {
        require(_TCR >= MCR, "CdpManager: Cannot redeem when TCR < MCR");
    }

    function _requireValidMaxFeePercentage(uint256 _maxFeePercentage) internal view {
        require(
            _maxFeePercentage >= redemptionFeeFloor && _maxFeePercentage <= DECIMAL_PRECISION,
            "Max fee percentage must be between redemption fee floor and 100%"
        );
    }

    // --- Governance Parameters ---

    /// @notice Set the staking reward split percentage
    /// @dev Only callable by authorized addresses
    /// @param _stakingRewardSplit New staking reward split percentage value
    function setStakingRewardSplit(uint256 _stakingRewardSplit) external requiresAuth {
        require(
            _stakingRewardSplit <= MAX_REWARD_SPLIT,
            "CDPManager: new staking reward split exceeds max"
        );

        syncGlobalAccountingAndGracePeriod();

        stakingRewardSplit = _stakingRewardSplit;
        emit StakingRewardSplitSet(_stakingRewardSplit);
    }

    /// @notice Set the minimum redemption fee floor percentage
    /// @dev Only callable by authorized addresses
    /// @param _redemptionFeeFloor New minimum redemption fee floor percentage
    function setRedemptionFeeFloor(uint256 _redemptionFeeFloor) external requiresAuth {
        require(
            _redemptionFeeFloor >= MIN_REDEMPTION_FEE_FLOOR,
            "CDPManager: new redemption fee floor is lower than minimum"
        );
        require(
            _redemptionFeeFloor <= DECIMAL_PRECISION,
            "CDPManager: new redemption fee floor is higher than maximum"
        );

        syncGlobalAccountingAndGracePeriod();

        redemptionFeeFloor = _redemptionFeeFloor;
        emit RedemptionFeeFloorSet(_redemptionFeeFloor);
    }

    /// @notice Set the minute decay factor for the redemption fee rate
    /// @dev Only callable by authorized addresses
    /// @param _minuteDecayFactor New minute decay factor value
    function setMinuteDecayFactor(uint256 _minuteDecayFactor) external requiresAuth {
        require(
            _minuteDecayFactor >= MIN_MINUTE_DECAY_FACTOR,
            "CDPManager: new minute decay factor out of range"
        );
        require(
            _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR,
            "CDPManager: new minute decay factor out of range"
        );

        syncGlobalAccountingAndGracePeriod();

        // decay first according to previous factor
        _decayBaseRate();

        // set new factor after decaying
        minuteDecayFactor = _minuteDecayFactor;
        emit MinuteDecayFactorSet(_minuteDecayFactor);
    }

    /// @notice Set the beta value that controls redemption fee rate
    /// @dev Only callable by authorized addresses
    /// @param _beta New beta value
    function setBeta(uint256 _beta) external requiresAuth {
        syncGlobalAccountingAndGracePeriod();

        _decayBaseRate();

        beta = _beta;
        emit BetaSet(_beta);
    }

    /// @notice Pause or unpause redemptions
    /// @dev Only callable by authorized addresses
    /// @param _paused True to pause redemptions, false to unpause

    function setRedemptionsPaused(bool _paused) external requiresAuth {
        syncGlobalAccountingAndGracePeriod();
        _decayBaseRate();

        redemptionsPaused = _paused;
        emit RedemptionsPaused(_paused);
    }

    // --- Cdp property getters ---

    /// @notice Get status of a Cdp. Named enum values can be found in ICdpManagerData.Status
    /// @param _cdpId ID of the Cdp to get status for
    /// @return Status code of the Cdp
    function getCdpStatus(bytes32 _cdpId) external view override returns (uint256) {
        return uint256(Cdps[_cdpId].status);
    }

    /// @notice Get stake value of a Cdp
    /// @param _cdpId ID of the Cdp to get stake for
    /// @return Stake value of the Cdp
    function getCdpStake(bytes32 _cdpId) external view override returns (uint256) {
        return Cdps[_cdpId].stake;
    }

    /// @notice Get stored debt value of a Cdp, in eBTC units
    /// @notice Cached value - does not include pending changes from redistributions
    /// @param _cdpId ID of the Cdp to get debt for
    /// @return Debt value of the Cdp in eBTC
    function getCdpDebt(bytes32 _cdpId) external view override returns (uint256) {
        return Cdps[_cdpId].debt;
    }

    /// @notice Get stored collateral value of a Cdp, in stETH shares
    /// @notice Cached value - does not include pending changes from staking yield
    /// @param _cdpId ID of the Cdp to get collateral for
    /// @return Collateral value of the Cdp in stETH shares
    function getCdpCollShares(bytes32 _cdpId) external view override returns (uint256) {
        return Cdps[_cdpId].coll;
    }

    /// @notice Get shares value of the liquidator gas incentive reward stored for a Cdp.
    /// @notice The value stored is processed when a Cdp closes.
    /// @dev Upon closing by borrower, This value is returned directly to the borrower.
    /// @dev Upon closing by a position manager, This value is returned directly to the position manager.
    /// @dev Upon a full liquidation, This value is given to liquidators upon fully liquidating the Cdp
    /// @dev Upon redemption, This value is sent to the CollSurplusPool for reclaiming by the borrower.
    /// @param _cdpId ID of the Cdp to get liquidator reward shares for
    /// @return Liquidator reward shares value of the Cdp
    function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view override returns (uint256) {
        return uint256(Cdps[_cdpId].liquidatorRewardShares);
    }

    // --- Cdp property setters, called by BorrowerOperations ---

    /// @notice Initialize all state for new Cdp
    /// @dev Only callable by BorrowerOperations, critical trust assumption
    /// @dev Requires Cdp to be already inserted into linked list correctly
    /// @param _cdpId ID of Cdp to initialize state for
    /// @param _debt Initial debt units of Cdp
    /// @param _coll Initial collateral shares of Cdp
    /// @param _liquidatorRewardShares Liquidator reward shares for Cdp liquidation gas stipend
    /// @param _borrower Address of the Cdp borrower
    function initializeCdp(
        bytes32 _cdpId,
        uint256 _debt,
        uint256 _coll,
        uint256 _liquidatorRewardShares,
        address _borrower
    ) external {
        _requireCallerIsBorrowerOperations();

        Cdps[_cdpId].debt = _debt;
        Cdps[_cdpId].coll = _coll;
        Cdps[_cdpId].status = Status.active;
        Cdps[_cdpId].liquidatorRewardShares = EbtcMath.toUint128(_liquidatorRewardShares);

        cdpStEthFeePerUnitIndex[_cdpId] = systemStEthFeePerUnitIndex; /// @audit We critically assume global accounting is synced here
        _updateRedistributedDebtIndex(_cdpId);
        uint256 stake = _updateStakeAndTotalStakes(_cdpId);

        // Previous debt and coll are known to be zero upon opening a new Cdp
        emit CdpUpdated(
            _cdpId,
            _borrower,
            msg.sender,
            0,
            0,
            _debt,
            _coll,
            stake,
            CdpOperation.openCdp
        );
    }

    /// @notice Set new Cdp debt and collateral values, updating stake accordingly
    /// @dev Only callable by BorrowerOperations, critical trust assumption
    /// @param _cdpId ID of Cdp to update state for
    /// @param _borrower Address of the Cdp borrower
    /// @param _coll Previous collateral shares of Cdp, before update
    /// @param _debt Previous debt units of Cdp, before update.
    /// @param _newColl New collateral shares of Cdp after update operation
    /// @param _newDebt New debt units of Cdp after update operation
    function updateCdp(
        bytes32 _cdpId,
        address _borrower,
        uint256 _coll,
        uint256 _debt,
        uint256 _newColl,
        uint256 _newDebt
    ) external {
        _requireCallerIsBorrowerOperations();

        _setCdpCollShares(_cdpId, _newColl);
        _setCdpDebt(_cdpId, _newDebt);

        uint256 stake = _updateStakeAndTotalStakes(_cdpId);

        emit CdpUpdated(
            _cdpId,
            _borrower,
            msg.sender,
            _debt,
            _coll,
            _newDebt,
            _newColl,
            stake,
            CdpOperation.adjustCdp
        );
    }

    /// @notice Set the collateral of a Cdp
    /// @param _cdpId The ID of the Cdp
    /// @param _newColl New collateral value, in stETH shares
    function _setCdpCollShares(bytes32 _cdpId, uint256 _newColl) internal {
        Cdps[_cdpId].coll = _newColl;
    }

    /// @notice Set the debt of a Cdp
    /// @param _cdpId The ID of the Cdp
    /// @param _newDebt New debt units value
    function _setCdpDebt(bytes32 _cdpId, uint256 _newDebt) internal {
        Cdps[_cdpId].debt = _newDebt;
    }
}

File 3 of 29 : CdpManagerStorage.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./Interfaces/ICdpManager.sol";
import "./Interfaces/ICollSurplusPool.sol";
import "./Interfaces/IEBTCToken.sol";
import "./Interfaces/ISortedCdps.sol";
import "./Dependencies/EbtcBase.sol";
import "./Dependencies/ReentrancyGuard.sol";
import "./Dependencies/ICollateralTokenOracle.sol";
import "./Dependencies/AuthNoOwner.sol";

/// @title CDP Manager storage and shared functions with LiquidationLibrary
/// @dev All features around Cdp management are split into separate parts to get around contract size limitations.
/// @dev Liquidation related functions are delegated to LiquidationLibrary contract code.
/// @dev Both CdpManager and LiquidationLibrary must maintain **the same storage layout**, so shared storage components
/// @dev and shared functions are added here in CdpManagerStorage to de-dup code
contract CdpManagerStorage is EbtcBase, ReentrancyGuard, ICdpManagerData, AuthNoOwner {
    // NOTE: No packing cause it's the last var, no need for u64
    uint128 public constant UNSET_TIMESTAMP = type(uint128).max;
    uint128 public constant MINIMUM_GRACE_PERIOD = 15 minutes;

    uint128 public lastGracePeriodStartTimestamp = UNSET_TIMESTAMP; // use max to signify
    uint128 public recoveryModeGracePeriodDuration = MINIMUM_GRACE_PERIOD;

    /// @notice Start the recovery mode grace period, if the system is in RM and the grace period timestamp has not already been set
    /// @dev Trusted function to allow BorrowerOperations actions to set RM Grace Period
    /// @dev Assumes BorrowerOperations has correctly calculated and passed in the new system TCR
    /// @dev To maintain CEI compliance we use this trusted function
    /// @param tcr The TCR to be checked whether Grace Period should be started
    function notifyStartGracePeriod(uint256 tcr) external {
        _requireCallerIsBorrowerOperations();
        _startGracePeriod(tcr);
    }

    /// @notice End the recovery mode grace period, if the system is no longer in RM
    /// @dev Trusted function to allow BorrowerOperations actions to set RM Grace Period
    /// @dev Assumes BorrowerOperations has correctly calculated and passed in the new system TCR
    /// @dev To maintain CEI compliance we use this trusted function
    /// @param tcr The TCR to be checked whether Grace Period should be ended
    function notifyEndGracePeriod(uint256 tcr) external {
        _requireCallerIsBorrowerOperations();
        _endGracePeriod(tcr);
    }

    /// @dev Internal notify called by Redemptions and Liquidations
    /// @dev Specified TCR is emitted for notification pruposes regardless of whether the Grace Period timestamp is set
    function _startGracePeriod(uint256 _tcr) internal {
        emit TCRNotified(_tcr);

        if (lastGracePeriodStartTimestamp == UNSET_TIMESTAMP) {
            lastGracePeriodStartTimestamp = uint128(block.timestamp);

            emit GracePeriodStart();
        }
    }

    /// @notice Clear RM Grace Period timestamp if it has been set
    /// @notice No input validation, calling function must confirm that the system is not in recovery mode to be valid
    /// @dev Specified TCR is emitted for notification pruposes regardless of whether the Grace Period timestamp is set
    /// @dev Internal notify called by Redemptions and Liquidations
    function _endGracePeriod(uint256 _tcr) internal {
        emit TCRNotified(_tcr);

        if (lastGracePeriodStartTimestamp != UNSET_TIMESTAMP) {
            lastGracePeriodStartTimestamp = UNSET_TIMESTAMP;

            emit GracePeriodEnd();
        }
    }

    function _syncGracePeriod() internal {
        uint256 price = priceFeed.fetchPrice();
        uint256 tcr = _getCachedTCR(price);
        bool isRecoveryMode = _checkRecoveryModeForTCR(tcr);

        if (isRecoveryMode) {
            _startGracePeriod(tcr);
        } else {
            _endGracePeriod(tcr);
        }
    }

    /// @dev Set RM grace period based on specified system collShares, system debt, and price
    /// @dev Variant for internal use in redemptions and liquidations
    function _syncGracePeriodForGivenValues(
        uint256 systemCollShares,
        uint256 systemDebt,
        uint256 price
    ) internal {
        // Compute TCR with specified values
        uint256 newTCR = EbtcMath._computeCR(
            collateral.getPooledEthByShares(systemCollShares),
            systemDebt,
            price
        );

        if (newTCR < CCR) {
            // Notify system is in RM
            _startGracePeriod(newTCR);
        } else {
            // Notify system is outside RM
            _endGracePeriod(newTCR);
        }
    }

    /// @notice Set grace period duratin
    /// @notice Permissioned governance function, must set grace period duration above hardcoded minimum
    /// @param _gracePeriod new grace period duration, in seconds
    function setGracePeriod(uint128 _gracePeriod) external requiresAuth {
        require(
            _gracePeriod >= MINIMUM_GRACE_PERIOD,
            "CdpManager: Grace period below minimum duration"
        );

        syncGlobalAccountingAndGracePeriod();
        recoveryModeGracePeriodDuration = _gracePeriod;
        emit GracePeriodDurationSet(_gracePeriod);
    }

    string public constant NAME = "CdpManager";

    // --- Connected contract declarations ---

    address public immutable borrowerOperationsAddress;

    ICollSurplusPool immutable collSurplusPool;

    IEBTCToken public immutable override ebtcToken;

    address public immutable liquidationLibrary;

    // A doubly linked list of Cdps, sorted by their sorted by their collateral ratios
    ISortedCdps public immutable sortedCdps;

    // --- Data structures ---

    uint256 public constant SECONDS_IN_ONE_MINUTE = 60;

    uint256 public constant MIN_REDEMPTION_FEE_FLOOR = (DECIMAL_PRECISION * 5) / 1000; // 0.5%
    uint256 public redemptionFeeFloor = MIN_REDEMPTION_FEE_FLOOR;
    bool public redemptionsPaused;
    /*
     * Half-life of 12h. 12h = 720 min
     * (1/2) = d^720 => d = (1/2)^(1/720)
     */
    uint256 public minuteDecayFactor = 999037758833783000;
    uint256 public constant MIN_MINUTE_DECAY_FACTOR = 1; // Non-zero
    uint256 public constant MAX_MINUTE_DECAY_FACTOR = 999999999999999999; // Corresponds to a very fast decay rate, but not too extreme

    uint256 internal immutable deploymentStartTime;

    /*
     * BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction,
     * in order to calc the new base rate from a redemption.
     * Corresponds to (1 / ALPHA) in the Liquity white paper.
     */
    uint256 public beta = 2;

    uint256 public baseRate;

    uint256 public stakingRewardSplit;

    // The timestamp of the latest fee operation (redemption or new EBTC issuance)
    uint256 public lastRedemptionTimestamp;

    mapping(bytes32 => Cdp) public Cdps;

    uint256 public override totalStakes;

    // Snapshot of the value of totalStakes, taken immediately after the latest liquidation and split fee claim
    uint256 public totalStakesSnapshot;

    // Snapshot of the total collateral across the ActivePool, immediately after the latest liquidation and split fee claim
    uint256 public totalCollateralSnapshot;

    /*
     * systemDebtRedistributionIndex track the sums of accumulated socialized liquidations per unit staked.
     * During its lifetime, each stake earns:
     *
     * A systemDebt increase  of ( stake * [systemDebtRedistributionIndex - systemDebtRedistributionIndex(0)] )
     *
     * Where systemDebtRedistributionIndex(0) are snapshots of systemDebtRedistributionIndex
     * for the active Cdp taken at the instant the stake was made
     */
    uint256 public systemDebtRedistributionIndex;

    // Map active cdps to their RewardSnapshot (eBTC debt redistributed)
    mapping(bytes32 => uint256) public cdpDebtRedistributionIndex;

    // Error trackers for the cdp redistribution calculation
    uint256 public lastEBTCDebtErrorRedistribution;

    /* Global Index for (Full Price Per Share) of underlying collateral token */
    uint256 public override stEthIndex;
    /* Global Fee accumulator (never decreasing) per stake unit in CDPManager, similar to systemDebtRedistributionIndex */
    uint256 public override systemStEthFeePerUnitIndex;
    /* Global Fee accumulator calculation error due to integer division, similar to redistribution calculation */
    uint256 public override systemStEthFeePerUnitIndexError;
    /* Individual CDP Fee accumulator tracker, used to calculate fee split distribution */
    mapping(bytes32 => uint256) public cdpStEthFeePerUnitIndex;

    /// @notice Initializes the contract with the provided addresses and sets up the required initial state
    /// @param _liquidationLibraryAddress The address of the Liquidation Library
    /// @param _authorityAddress The address of the Authority
    /// @param _borrowerOperationsAddress The address of Borrower Operations
    /// @param _collSurplusPool The address of the Collateral Surplus Pool
    /// @param _ebtcToken The address of the eBTC Token contract
    /// @param _sortedCdps The address of the Sorted CDPs contract
    /// @param _activePool The address of the Active Pool
    /// @param _priceFeed The address of the Price Feed
    /// @param _collateral The address of the Collateral token
    constructor(
        address _liquidationLibraryAddress,
        address _authorityAddress,
        address _borrowerOperationsAddress,
        address _collSurplusPool,
        address _ebtcToken,
        address _sortedCdps,
        address _activePool,
        address _priceFeed,
        address _collateral
    ) EbtcBase(_activePool, _priceFeed, _collateral) {
        deploymentStartTime = block.timestamp;
        liquidationLibrary = _liquidationLibraryAddress;

        _initializeAuthority(_authorityAddress);

        borrowerOperationsAddress = _borrowerOperationsAddress;
        collSurplusPool = ICollSurplusPool(_collSurplusPool);
        ebtcToken = IEBTCToken(_ebtcToken);
        sortedCdps = ISortedCdps(_sortedCdps);
    }

    /// @notice BorrowerOperations and CdpManager share reentrancy status by confirming the other's locked flag before beginning operation
    /// @dev This is an alternative to the more heavyweight solution of both being able to set the reentrancy flag on a 3rd contract.
    modifier nonReentrantSelfAndBOps() {
        require(locked == OPEN, "CdpManager: Reentrancy in nonReentrant call");
        require(
            ReentrancyGuard(borrowerOperationsAddress).locked() == OPEN,
            "BorrowerOperations: Reentrancy in nonReentrant call"
        );

        locked = LOCKED;

        _;

        locked = OPEN;
    }

    function _closeCdp(bytes32 _cdpId, Status closedStatus) internal {
        _closeCdpWithoutRemovingSortedCdps(_cdpId, closedStatus);
        sortedCdps.remove(_cdpId);
    }

    function _closeCdpWithoutRemovingSortedCdps(bytes32 _cdpId, Status closedStatus) internal {
        require(
            closedStatus != Status.nonExistent && closedStatus != Status.active,
            "CdpManagerStorage: close non-exist or non-active CDP!"
        );

        uint256 cdpIdsArrayLength = getActiveCdpsCount();
        _requireMoreThanOneCdpInSystem(cdpIdsArrayLength);

        _removeStake(_cdpId);

        Cdps[_cdpId].status = closedStatus;
        Cdps[_cdpId].coll = 0;
        Cdps[_cdpId].debt = 0;
        Cdps[_cdpId].liquidatorRewardShares = 0;

        cdpDebtRedistributionIndex[_cdpId] = 0;
        cdpStEthFeePerUnitIndex[_cdpId] = 0;
    }

    /*
     * Updates snapshots of system total stakes and total collateral,
     * excluding a given collateral remainder from the calculation.
     * Used in a liquidation sequence.
     *
     * The calculation excludes a portion of collateral that is in the ActivePool:
     *
     * the total stETH liquidator reward compensation from the liquidation sequence
     *
     * The stETH as compensation must be excluded as it is always sent out at the very end of the liquidation sequence.
     */
    function _updateSystemSnapshotsExcludeCollRemainder(uint256 _collRemainder) internal {
        uint256 _totalStakesSnapshot = totalStakes;
        totalStakesSnapshot = _totalStakesSnapshot;

        uint256 _totalCollateralSnapshot = activePool.getSystemCollShares() - _collRemainder;
        totalCollateralSnapshot = _totalCollateralSnapshot;

        emit SystemSnapshotsUpdated(_totalStakesSnapshot, _totalCollateralSnapshot);
    }

    /// @dev get the pending Cdp debt "reward" (i.e. the amount of extra debt assigned to the Cdp) from liquidation redistribution events, earned by their stake
    function _getPendingRedistributedDebt(
        bytes32 _cdpId
    ) internal view returns (uint256 pendingEBTCDebtReward, uint256 _debtIndexDiff) {
        Cdp storage cdp = Cdps[_cdpId];

        if (cdp.status != Status.active) {
            return (0, 0);
        }

        _debtIndexDiff = systemDebtRedistributionIndex - cdpDebtRedistributionIndex[_cdpId];

        if (_debtIndexDiff > 0) {
            pendingEBTCDebtReward = (cdp.stake * _debtIndexDiff) / DECIMAL_PRECISION;
        } else {
            return (0, 0);
        }
    }

    /*
     * A Cdp has pending redistributed debt if its snapshot is less than the current rewards per-unit-staked sum:
     * this indicates that redistributions have occured since the snapshot was made, and the user therefore has
     * pending debt
     */
    function _hasRedistributedDebt(bytes32 _cdpId) internal view returns (bool) {
        if (Cdps[_cdpId].status != Status.active) {
            return false;
        }

        return (cdpDebtRedistributionIndex[_cdpId] < systemDebtRedistributionIndex);
    }

    /// @dev Sync Cdp debt redistribution index to global value
    function _updateRedistributedDebtIndex(bytes32 _cdpId) internal {
        uint256 _systemDebtRedistributionIndex = systemDebtRedistributionIndex;

        cdpDebtRedistributionIndex[_cdpId] = _systemDebtRedistributionIndex;
        emit CdpDebtRedistributionIndexUpdated(_cdpId, _systemDebtRedistributionIndex);
    }

    /// @dev Calculate the new collateral and debt values for a given CDP, based on pending state changes
    function _syncAccounting(bytes32 _cdpId) internal {
        // Ensure global states like systemStEthFeePerUnitIndex get updated in a timely fashion
        // whenever there is a CDP modification operation,
        // such as opening, closing, adding collateral, repaying debt, or liquidating
        _syncGlobalAccounting();

        uint256 _oldPerUnitCdp = cdpStEthFeePerUnitIndex[_cdpId];
        uint256 _systemStEthFeePerUnitIndex = systemStEthFeePerUnitIndex;

        (
            uint256 _newColl,
            uint256 _newDebt,
            uint256 _feeSplitDistributed,
            uint _pendingDebt,
            uint256 _debtIndexDelta
        ) = _calcSyncedAccounting(_cdpId, _oldPerUnitCdp, _systemStEthFeePerUnitIndex);

        // If any collShares or debt changes occured
        if (_feeSplitDistributed > 0 || _debtIndexDelta > 0) {
            Cdp storage _cdp = Cdps[_cdpId];

            uint prevCollShares = _cdp.coll;
            uint256 prevDebt = _cdp.debt;

            // Apply Fee Split
            if (_feeSplitDistributed > 0) {
                _applyAccumulatedFeeSplit(
                    _cdpId,
                    _newColl,
                    _feeSplitDistributed,
                    _oldPerUnitCdp,
                    _systemStEthFeePerUnitIndex
                );
            }

            // Apply Debt Redistribution
            if (_debtIndexDelta > 0) {
                _updateRedistributedDebtIndex(_cdpId);

                if (prevDebt != _newDebt) {
                    {
                        // Apply pending debt redistribution to this CDP
                        _cdp.debt = _newDebt;
                    }
                }
            }
            emit CdpUpdated(
                _cdpId,
                ISortedCdps(sortedCdps).getOwnerAddress(_cdpId),
                msg.sender,
                prevDebt,
                prevCollShares,
                _newDebt,
                _newColl,
                _cdp.stake,
                CdpOperation.syncAccounting
            );
        }

        // sync per stake index for given CDP
        if (_oldPerUnitCdp != _systemStEthFeePerUnitIndex) {
            cdpStEthFeePerUnitIndex[_cdpId] = _systemStEthFeePerUnitIndex;
        }
    }

    // Remove borrower's stake from the totalStakes sum, and set their stake to 0
    function _removeStake(bytes32 _cdpId) internal {
        uint256 _newTotalStakes = totalStakes - Cdps[_cdpId].stake;
        totalStakes = _newTotalStakes;
        Cdps[_cdpId].stake = 0;
        emit TotalStakesUpdated(_newTotalStakes);
    }

    // Update borrower's stake based on their latest collateral value
    // and update totalStakes accordingly as well
    function _updateStakeAndTotalStakes(bytes32 _cdpId) internal returns (uint256) {
        (uint256 newStake, uint256 oldStake) = _updateStakeForCdp(_cdpId);

        uint256 _newTotalStakes = totalStakes + newStake - oldStake;
        totalStakes = _newTotalStakes;

        emit TotalStakesUpdated(_newTotalStakes);

        return newStake;
    }

    // Update borrower's stake based on their latest collateral value
    function _updateStakeForCdp(bytes32 _cdpId) internal returns (uint256, uint256) {
        Cdp storage _cdp = Cdps[_cdpId];
        uint256 newStake = _computeNewStake(_cdp.coll);
        uint256 oldStake = _cdp.stake;
        _cdp.stake = newStake;

        return (newStake, oldStake);
    }

    // Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at the last liquidation
    function _computeNewStake(uint256 _coll) internal view returns (uint256) {
        uint256 stake;
        if (totalCollateralSnapshot == 0) {
            stake = _coll;
        } else {
            /*
             * The following check holds true because:
             * - The system always contains >= 1 cdp
             * - When we close or liquidate a cdp, we redistribute the pending rewards,
             * so if all cdps were closed/liquidated,
             * rewards would’ve been emptied and totalCollateralSnapshot would be zero too.
             */
            require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");
            stake = (_coll * totalStakesSnapshot) / totalCollateralSnapshot;
        }
        return stake;
    }

    // --- Recovery Mode and TCR functions ---

    // Calculate TCR given an price, and the entire system coll and debt.
    function _computeTCRWithGivenSystemValues(
        uint256 _systemCollShares,
        uint256 _systemDebt,
        uint256 _price
    ) internal view returns (uint256) {
        uint256 _totalColl = collateral.getPooledEthByShares(_systemCollShares);
        return EbtcMath._computeCR(_totalColl, _systemDebt, _price);
    }

    // --- Staking-Reward Fee split functions ---

    /// @notice Claim split fee if there is staking-reward coming
    /// @notice and update global index & fee-per-unit variables
    /// @dev only BorrowerOperations is allowed to call this
    /// @dev otherwise use syncGlobalAccountingAndGracePeriod()
    function syncGlobalAccounting() external {
        _requireCallerIsBorrowerOperations();
        _syncGlobalAccounting();
    }

    function _syncGlobalAccounting() internal {
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
        _syncStEthIndex(_oldIndex, _newIndex);
        if (_newIndex > _oldIndex && totalStakes > 0) {
            (
                uint256 _feeTaken,
                uint256 _newFeePerUnit,
                uint256 _perUnitError
            ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
            _takeSplitAndUpdateFeePerUnit(_feeTaken, _newFeePerUnit, _perUnitError);
            _updateSystemSnapshotsExcludeCollRemainder(0);
        }
    }

    /// @notice Claim fee split, if there is staking-reward coming
    /// @notice and update global index & fee-per-unit variables
    /// @notice and toggles Grace Period accordingly.
    /// @dev Call this if you want to help eBTC system to accrue split fee
    function syncGlobalAccountingAndGracePeriod() public {
        _syncGlobalAccounting(); // Apply // Could trigger RM
        _syncGracePeriod(); // Synch Grace Period
    }

    /// @return existing(old) local stETH index AND
    /// @return current(new) stETH index from collateral token
    function _readStEthIndex() internal view returns (uint256, uint256) {
        return (stEthIndex, collateral.getPooledEthByShares(DECIMAL_PRECISION));
    }

    // Update the global index via collateral token
    function _syncStEthIndex(uint256 _oldIndex, uint256 _newIndex) internal {
        if (_newIndex != _oldIndex) {
            stEthIndex = _newIndex;
            emit StEthIndexUpdated(_oldIndex, _newIndex, block.timestamp);
        }
    }

    /// @notice Calculate fee for given pair of collateral indexes
    /// @param _newIndex The value synced with stETH.getPooledEthByShares(1e18)
    /// @param _prevIndex The cached global value of `stEthIndex`
    /// @return _feeTaken The fee split in collateral token which will be deduced from current total system collateral
    /// @return _deltaFeePerUnit The fee split increase per unit, used to added to `systemStEthFeePerUnitIndex`
    /// @return _perUnitError The fee split calculation error, used to update `systemStEthFeePerUnitIndexError`
    function calcFeeUponStakingReward(
        uint256 _newIndex,
        uint256 _prevIndex
    ) public view returns (uint256, uint256, uint256) {
        require(_newIndex > _prevIndex, "CDPManager: only take fee with bigger new index");
        uint256 deltaIndex = _newIndex - _prevIndex;
        uint256 deltaIndexFees = (deltaIndex * stakingRewardSplit) / MAX_REWARD_SPLIT;

        // we take the fee for all CDPs immediately which is scaled by index precision
        uint256 _deltaFeeSplit = deltaIndexFees * getSystemCollShares();
        uint256 _cachedAllStakes = totalStakes;
        // return the values to update the global fee accumulator
        uint256 _feeTaken = collateral.getSharesByPooledEth(_deltaFeeSplit) / DECIMAL_PRECISION;
        uint256 _deltaFeeSplitShare = (_feeTaken * DECIMAL_PRECISION) +
            systemStEthFeePerUnitIndexError;
        uint256 _deltaFeePerUnit = _deltaFeeSplitShare / _cachedAllStakes;
        uint256 _perUnitError = _deltaFeeSplitShare - (_deltaFeePerUnit * _cachedAllStakes);
        return (_feeTaken, _deltaFeePerUnit, _perUnitError);
    }

    // Take the cut from staking reward
    // and update global fee-per-unit accumulator
    function _takeSplitAndUpdateFeePerUnit(
        uint256 _feeTaken,
        uint256 _newPerUnit,
        uint256 _newErrorPerUnit
    ) internal {
        uint256 _oldPerUnit = systemStEthFeePerUnitIndex;

        systemStEthFeePerUnitIndex = _newPerUnit;
        systemStEthFeePerUnitIndexError = _newErrorPerUnit;

        require(activePool.getSystemCollShares() > _feeTaken, "CDPManager: fee split is too big");
        activePool.allocateSystemCollSharesToFeeRecipient(_feeTaken);

        emit CollateralFeePerUnitUpdated(_oldPerUnit, _newPerUnit, _feeTaken);
    }

    // Apply accumulated fee split distributed to the CDP
    // and update its accumulator tracker accordingly
    function _applyAccumulatedFeeSplit(
        bytes32 _cdpId,
        uint256 _newColl,
        uint256 _feeSplitDistributed,
        uint256 _oldPerUnitCdp,
        uint256 _systemStEthFeePerUnitIndex
    ) internal {
        // apply split fee to given CDP
        Cdps[_cdpId].coll = _newColl;

        emit CdpFeeSplitApplied(
            _cdpId,
            _oldPerUnitCdp,
            _systemStEthFeePerUnitIndex,
            _feeSplitDistributed,
            _newColl
        );
    }

    /// @notice Calculate the applied split fee(scaled by 1e18) and the resulting CDP collateral share after applied
    /// @param _cdpId The Cdp to which the calculated split fee is going to be applied
    /// @param _systemStEthFeePerUnitIndex The fee-per-stake-unit value to be used in fee split calculation, could be result of calcFeeUponStakingReward()
    /// @return _feeSplitDistributed The applied fee split to the specified Cdp (scaled up by 1e18)
    /// @return _cdpCol The new collateral share of the specified Cdp after fe split applied
    function getAccumulatedFeeSplitApplied(
        bytes32 _cdpId,
        uint256 _systemStEthFeePerUnitIndex
    ) public view returns (uint256, uint256) {
        uint256 _cdpStEthFeePerUnitIndex = cdpStEthFeePerUnitIndex[_cdpId];
        uint256 _cdpCol = Cdps[_cdpId].coll;

        if (
            _cdpStEthFeePerUnitIndex == 0 ||
            _cdpCol == 0 ||
            _cdpStEthFeePerUnitIndex == _systemStEthFeePerUnitIndex
        ) {
            return (0, _cdpCol);
        }

        uint256 _feeSplitDistributed = Cdps[_cdpId].stake *
            (_systemStEthFeePerUnitIndex - _cdpStEthFeePerUnitIndex);

        uint256 _scaledCdpColl = _cdpCol * DECIMAL_PRECISION;

        if (_scaledCdpColl > _feeSplitDistributed) {
            return (
                _feeSplitDistributed,
                (_scaledCdpColl - _feeSplitDistributed) / DECIMAL_PRECISION
            );
        } else {
            // extreme unlikely case to skip fee split on this CDP to avoid revert
            return (0, _cdpCol);
        }
    }

    // -- Modifier functions --
    function _requireCdpIsActive(bytes32 _cdpId) internal view {
        require(Cdps[_cdpId].status == Status.active, "CdpManager: Cdp does not exist or is closed");
    }

    function _requireMoreThanOneCdpInSystem(uint256 CdpOwnersArrayLength) internal view {
        require(
            CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,
            "CdpManager: Only one cdp in the system"
        );
    }

    function _requireCallerIsBorrowerOperations() internal view {
        require(
            msg.sender == borrowerOperationsAddress,
            "CdpManager: Caller is not the BorrowerOperations contract"
        );
    }

    // --- Helper functions ---

    /// @notice Return the Nominal Collateral Ratio (NICR) of the specified Cdp as "cached view" (maybe outdated).
    /// @dev Takes a cdp's pending coll and debt rewards from redistributions into account.
    /// @param _cdpId The CdpId whose NICR to be queried
    /// @return The Nominal Collateral Ratio (NICR) of the specified Cdp.
    /// @dev Use getSyncedNominalICR() instead if pending fee split and debt redistribution should be considered
    function getCachedNominalICR(bytes32 _cdpId) external view returns (uint256) {
        (uint256 currentEBTCDebt, uint256 currentCollShares) = getSyncedDebtAndCollShares(_cdpId);

        uint256 NICR = EbtcMath._computeNominalCR(currentCollShares, currentEBTCDebt);
        return NICR;
    }

    /// @notice Return the Nominal Collateral Ratio (NICR) of the specified Cdp.
    /// @dev Takes a cdp's pending coll and debt rewards as well as stETH Index into account.
    /// @param _cdpId The CdpId whose NICR to be queried
    /// @return The Nominal Collateral Ratio (NICR) of the specified Cdp with fee split and debt redistribution considered.
    function getSyncedNominalICR(bytes32 _cdpId) external view returns (uint256) {
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
        (, uint256 _newGlobalSplitIdx, ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
        (uint256 _newColl, uint256 _newDebt, , uint256 _pendingDebt, ) = _calcSyncedAccounting(
            _cdpId,
            cdpStEthFeePerUnitIndex[_cdpId],
            _newGlobalSplitIdx /// NOTE: This is latest index
        );

        uint256 NICR = EbtcMath._computeNominalCR(_newColl, _newDebt);
        return NICR;
    }

    /// @notice Return the Individual Collateral Ratio (ICR) of the specified Cdp as "cached view" (maybe outdated).
    /// @param _cdpId The CdpId whose ICR to be queried
    /// @return The Individual Collateral Ratio (ICR) of the specified Cdp.
    /// @dev Use getSyncedICR() instead if pending fee split and debt redistribution should be considered
    function getCachedICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {
        (uint256 currentEBTCDebt, uint256 currentCollShares) = getSyncedDebtAndCollShares(_cdpId);
        uint256 ICR = _calculateCR(currentCollShares, currentEBTCDebt, _price);
        return ICR;
    }

    function _calculateCR(
        uint256 currentCollShare,
        uint256 currentDebt,
        uint256 _price
    ) internal view returns (uint256) {
        uint256 _underlyingCollateral = collateral.getPooledEthByShares(currentCollShare);
        return EbtcMath._computeCR(_underlyingCollateral, currentDebt, _price);
    }

    /// @notice Return the pending extra debt assigned to the Cdp from liquidation redistribution, calcualted by Cdp's stake
    /// @param _cdpId The CdpId whose pending debt redistribution to be queried
    /// @return pendingEBTCDebtReward The pending debt redistribution of the specified Cdp.
    function getPendingRedistributedDebt(
        bytes32 _cdpId
    ) public view returns (uint256 pendingEBTCDebtReward) {
        (uint256 _pendingDebt, ) = _getPendingRedistributedDebt(_cdpId);
        return _pendingDebt;
    }

    /// @return Whether the debt redistribution tracking index of the specified Cdp is less than the global tracking one (meaning it might get pending debt redistribution)
    /// @param _cdpId The CdpId whose debt redistribution tracking index to be queried against the gloabl one
    function hasPendingRedistributedDebt(bytes32 _cdpId) public view returns (bool) {
        return _hasRedistributedDebt(_cdpId);
    }

    // Return the Cdps entire debt and coll struct
    function _getSyncedDebtAndCollShares(
        bytes32 _cdpId
    ) internal view returns (CdpDebtAndCollShares memory) {
        (uint256 entireDebt, uint256 entireColl) = getSyncedDebtAndCollShares(_cdpId);
        return CdpDebtAndCollShares(entireDebt, entireColl);
    }

    /// @notice Calculate the Cdps entire debt and coll, including pending debt redistributions and collateral reduction from split fee.
    /// @param _cdpId The CdpId to be queried
    /// @return debt The total debt value of the Cdp including debt redistribution considered
    /// @return coll The total collateral value of the Cdp including possible fee split considered
    /// @dev Should always use this as the first(default) choice for Cdp position size query
    function getSyncedDebtAndCollShares(
        bytes32 _cdpId
    ) public view returns (uint256 debt, uint256 coll) {
        (uint256 _newColl, uint256 _newDebt, , , ) = _calcSyncedAccounting(
            _cdpId,
            cdpStEthFeePerUnitIndex[_cdpId],
            systemStEthFeePerUnitIndex
        );
        coll = _newColl;
        debt = _newDebt;
    }

    /// @dev calculate pending global state change to be applied:
    /// @return split fee taken (if any) AND
    /// @return new split index per stake unit AND
    /// @return new split index error
    function _calcSyncedGlobalAccounting(
        uint256 _newIndex,
        uint256 _oldIndex
    ) internal view returns (uint256, uint256, uint256) {
        if (_newIndex > _oldIndex && totalStakes > 0) {
            /// @audit-ok We don't take the fee if we had a negative rebase
            (
                uint256 _feeTaken,
                uint256 _deltaFeePerUnit,
                uint256 _perUnitError
            ) = calcFeeUponStakingReward(_newIndex, _oldIndex);

            // calculate new split per stake unit
            uint256 _newPerUnit = systemStEthFeePerUnitIndex + _deltaFeePerUnit;
            return (_feeTaken, _newPerUnit, _perUnitError);
        } else {
            return (0, systemStEthFeePerUnitIndex, systemStEthFeePerUnitIndexError);
        }
    }

    /// @dev calculate pending state change to be applied for given CDP and global split index(typically already synced):
    /// @return new CDP collateral share after pending change applied
    /// @return new CDP debt after pending change applied
    /// @return split fee applied to given CDP
    /// @return redistributed debt applied to given CDP
    /// @return delta between debt redistribution index of given CDP and global tracking index
    function _calcSyncedAccounting(
        bytes32 _cdpId,
        uint256 _cdpPerUnitIdx,
        uint256 _systemStEthFeePerUnitIndex
    ) internal view returns (uint256, uint256, uint256, uint256, uint256) {
        uint256 _feeSplitApplied;
        uint256 _newCollShare = Cdps[_cdpId].coll;

        // processing split fee to be applied
        if (_cdpPerUnitIdx != _systemStEthFeePerUnitIndex && _cdpPerUnitIdx > 0) {
            (
                uint256 _feeSplitDistributed,
                uint256 _newCollShareAfter
            ) = getAccumulatedFeeSplitApplied(_cdpId, _systemStEthFeePerUnitIndex);
            _feeSplitApplied = _feeSplitDistributed;
            _newCollShare = _newCollShareAfter;
        }

        // processing redistributed debt to be applied
        (
            uint256 _newDebt,
            uint256 pendingDebtRedistributed,
            uint256 _debtIndexDelta
        ) = _getSyncedCdpDebtAndRedistribution(_cdpId);

        return (
            _newCollShare,
            _newDebt,
            _feeSplitApplied,
            pendingDebtRedistributed,
            _debtIndexDelta
        );
    }

    /// @return CDP debt and pending redistribution from liquidation applied
    function _getSyncedCdpDebtAndRedistribution(
        bytes32 _cdpId
    ) internal view returns (uint256, uint256, uint256) {
        (uint256 pendingDebtRedistributed, uint256 _debtIndexDelta) = _getPendingRedistributedDebt(
            _cdpId
        );
        uint256 _newDebt = Cdps[_cdpId].debt;
        if (pendingDebtRedistributed > 0) {
            _newDebt = _newDebt + pendingDebtRedistributed;
        }
        return (_newDebt, pendingDebtRedistributed, _debtIndexDelta);
    }

    /// @notice Calculate the Cdps entire debt, including pending debt redistributions.
    /// @param _cdpId The CdpId to be queried
    /// @return _newDebt The total debt value of the Cdp including debt redistribution considered
    /// @dev Should always use this as the first(default) choice for Cdp debt query
    function getSyncedCdpDebt(bytes32 _cdpId) public view returns (uint256) {
        (uint256 _newDebt, , ) = _getSyncedCdpDebtAndRedistribution(_cdpId);
        return _newDebt;
    }

    /// @notice Calculate the Cdps entire collateral, including pending fee split to be applied
    /// @param _cdpId The CdpId to be queried
    /// @return _newColl The total collateral value of the Cdp including fee split considered
    /// @dev Should always use this as the first(default) choice for Cdp collateral query
    function getSyncedCdpCollShares(bytes32 _cdpId) public view returns (uint256) {
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
        (, uint256 _newGlobalSplitIdx, ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
        (uint256 _newColl, , , , ) = _calcSyncedAccounting(
            _cdpId,
            cdpStEthFeePerUnitIndex[_cdpId],
            _newGlobalSplitIdx
        );
        return _newColl;
    }

    /// @notice Calculate the Cdps ICR, including pending debt distribution and fee split to be applied
    /// @param _cdpId The CdpId to be queried
    /// @param _price The ETH:eBTC price to be used in ICR calculation
    /// @return The ICR of the Cdp including debt distribution and fee split considered
    /// @dev Should always use this as the first(default) choice for Cdp ICR query
    function getSyncedICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {
        uint256 _debt = getSyncedCdpDebt(_cdpId);
        uint256 _collShare = getSyncedCdpCollShares(_cdpId);
        return _calculateCR(_collShare, _debt, _price);
    }

    /// @notice return system collateral share, including pending fee split to be taken
    function getSyncedSystemCollShares() public view returns (uint256) {
        (uint256 _oldIndex, uint256 _newIndex) = _readStEthIndex();
        (uint256 _feeTaken, , ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);

        uint256 _systemCollShare = activePool.getSystemCollShares();
        if (_feeTaken > 0) {
            _systemCollShare = _systemCollShare - _feeTaken;
        }
        return _systemCollShare;
    }

    /// @notice Calculate the TCR, including pending debt distribution and fee split to be taken
    /// @param _price The ETH:eBTC price to be used in TCR calculation
    /// @return The TCR of the eBTC system including debt distribution and fee split considered
    /// @dev Should always use this as the first(default) choice for TCR query
    function getSyncedTCR(uint256 _price) public view returns (uint256) {
        uint256 _systemCollShare = getSyncedSystemCollShares();
        uint256 _systemDebt = activePool.getSystemDebt();
        return _calculateCR(_systemCollShare, _systemDebt, _price);
    }

    /// @notice Get the count of active Cdps in the system
    /// @return The number of current active Cdps (not closed) in the system.
    function getActiveCdpsCount() public view override returns (uint256) {
        return sortedCdps.getSize();
    }

    /// @param icr The ICR of a Cdp to check if liquidatable
    /// @param tcr The TCR of the eBTC system used to determine if Recovery Mode is triggered
    /// @return whether the Cdp of specified icr is liquidatable with specified tcr
    /// @dev The flag will only be set to true if enough time has passed since Grace Period starts
    function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) public view returns (bool) {
        return _checkICRAgainstTCR(icr, tcr) && _recoveryModeGracePeriodPassed();
    }

    /// @dev Check if enough time has passed for grace period after enabled
    function _recoveryModeGracePeriodPassed() internal view returns (bool) {
        // we have waited enough
        uint128 cachedLastGracePeriodStartTimestamp = lastGracePeriodStartTimestamp;
        return
            cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP &&
            block.timestamp > cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriodDuration;
    }
}

File 4 of 29 : AuthNoOwner.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

import {Authority} from "./Authority.sol";

/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
/// @author Modified by BadgerDAO to remove owner
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
contract AuthNoOwner {
    event AuthorityUpdated(address indexed user, Authority indexed newAuthority);

    Authority private _authority;
    bool private _authorityInitialized;

    modifier requiresAuth() virtual {
        require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");

        _;
    }

    function authority() public view returns (Authority) {
        return _authority;
    }

    function authorityInitialized() public view returns (bool) {
        return _authorityInitialized;
    }

    function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
        Authority auth = _authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.

        // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
        // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
        return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig));
    }

    /// @notice Changed constructor to initialize to allow flexiblity of constructor vs initializer use
    /// @notice sets authorityInitiailzed flag to ensure only one use of
    function _initializeAuthority(address newAuthority) internal {
        require(address(_authority) == address(0), "Auth: authority is non-zero");
        require(!_authorityInitialized, "Auth: authority already initialized");

        _authority = Authority(newAuthority);
        _authorityInitialized = true;

        emit AuthorityUpdated(address(this), Authority(newAuthority));
    }
}

File 5 of 29 : Authority.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

/// @notice A generic interface for a contract which provides authorization data to an Auth instance.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
interface Authority {
    function canCall(address user, address target, bytes4 functionSig) external view returns (bool);
}

File 6 of 29 : BaseMath.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract BaseMath {
    uint256 public constant DECIMAL_PRECISION = 1e18;
}

File 7 of 29 : EbtcBase.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./BaseMath.sol";
import "./EbtcMath.sol";
import "../Interfaces/IActivePool.sol";
import "../Interfaces/IPriceFeed.sol";
import "../Interfaces/IEbtcBase.sol";
import "../Dependencies/ICollateralToken.sol";

/*
 * Base contract for CdpManager, BorrowerOperations. Contains global system constants and
 * common functions.
 */
contract EbtcBase is BaseMath, IEbtcBase {
    // Collateral Ratio applied for Liquidation Incentive
    // i.e., liquidator repay $1 worth of debt to get back $1.03 worth of collateral
    uint256 public constant LICR = 1030000000000000000; // 103%

    // Minimum collateral ratio for individual cdps
    uint256 public constant MCR = 1100000000000000000; // 110%

    // Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered.
    uint256 public constant CCR = 1250000000000000000; // 125%

    // Amount of stETH collateral to be locked in active pool on opening cdps
    uint256 public constant LIQUIDATOR_REWARD = 2e17;

    // Minimum amount of stETH collateral a CDP must have
    uint256 public constant MIN_NET_STETH_BALANCE = 2e18;

    uint256 public constant PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%

    uint256 public constant BORROWING_FEE_FLOOR = 0; // 0.5%

    uint256 public constant STAKING_REWARD_SPLIT = 5_000; // taking 50% cut from staking reward

    uint256 public constant MAX_REWARD_SPLIT = 10_000;

    uint256 public constant MIN_CHANGE = 1000;

    IActivePool public immutable activePool;

    IPriceFeed public immutable override priceFeed;

    // the only collateral token allowed in CDP
    ICollateralToken public immutable collateral;

    /// @notice Initializes the contract with the provided addresses
    /// @param _activePoolAddress The address of the ActivePool contract
    /// @param _priceFeedAddress The address of the PriceFeed contract
    /// @param _collateralAddress The address of the CollateralToken contract
    constructor(address _activePoolAddress, address _priceFeedAddress, address _collateralAddress) {
        activePool = IActivePool(_activePoolAddress);
        priceFeed = IPriceFeed(_priceFeedAddress);
        collateral = ICollateralToken(_collateralAddress);
    }

    // --- Gas compensation functions ---

    function _calcNetStEthBalance(uint256 _stEthBalance) internal pure returns (uint256) {
        return _stEthBalance - LIQUIDATOR_REWARD;
    }

    /// @notice Get the entire system collateral
    /// @notice Entire system collateral = collateral allocated to system in ActivePool, using it's internal accounting
    /// @dev Collateral tokens stored in ActivePool for liquidator rewards, fees, or coll in CollSurplusPool, are not included
    function getSystemCollShares() public view returns (uint256 entireSystemColl) {
        return (activePool.getSystemCollShares());
    }

    /**
        @notice Get the entire system debt
        @notice Entire system collateral = collateral stored in ActivePool, using their internal accounting
     */
    function _getSystemDebt() internal view returns (uint256 entireSystemDebt) {
        return (activePool.getSystemDebt());
    }

    function _getCachedTCR(uint256 _price) internal view returns (uint256 TCR) {
        (TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);
    }

    function _getTCRWithSystemDebtAndCollShares(
        uint256 _price
    ) internal view returns (uint256 TCR, uint256 _coll, uint256 _debt) {
        uint256 systemCollShares = getSystemCollShares();
        uint256 systemDebt = _getSystemDebt();

        uint256 _systemStEthBalance = collateral.getPooledEthByShares(systemCollShares);
        TCR = EbtcMath._computeCR(_systemStEthBalance, systemDebt, _price);

        return (TCR, systemCollShares, systemDebt);
    }

    function _checkRecoveryMode(uint256 _price) internal view returns (bool) {
        return _checkRecoveryModeForTCR(_getCachedTCR(_price));
    }

    function _checkRecoveryModeForTCR(uint256 _tcr) internal view returns (bool) {
        return _tcr < CCR;
    }

    function _requireUserAcceptsFee(
        uint256 _fee,
        uint256 _amount,
        uint256 _maxFeePercentage
    ) internal pure {
        uint256 feePercentage = (_fee * DECIMAL_PRECISION) / _amount;
        require(feePercentage <= _maxFeePercentage, "Fee exceeded provided maximum");
    }

    // Convert debt denominated in ETH to debt denominated in BTC given that _price is ETH/BTC
    // _debt is denominated in ETH
    // _price is ETH/BTC
    function _convertDebtDenominationToBtc(
        uint256 _debt,
        uint256 _price
    ) internal pure returns (uint256) {
        return (_debt * _price) / DECIMAL_PRECISION;
    }

    /// @dev return true if given ICR is qualified for liquidation compared to configured threshold
    /// @dev this function ONLY checks numbers not check grace period switch for Recovery Mode
    function _checkICRAgainstLiqThreshold(uint256 _icr, uint _tcr) internal view returns (bool) {
        // Either undercollateralized
        // OR, it's RM AND they meet the requirement
        // Swapped Requirement && RM to save gas
        return
            _checkICRAgainstMCR(_icr) ||
            (_checkICRAgainstTCR(_icr, _tcr) && _checkRecoveryModeForTCR(_tcr));
    }

    /// @dev return true if given ICR is qualified for liquidation compared to MCR
    function _checkICRAgainstMCR(uint256 _icr) internal view returns (bool) {
        return _icr < MCR;
    }

    /// @dev return true if given ICR is qualified for liquidation compared to TCR
    /// @dev typically used in Recovery Mode
    function _checkICRAgainstTCR(uint256 _icr, uint _tcr) internal view returns (bool) {
        /// @audit is _icr <= _tcr more dangerous for overal system safety?
        /// @audit Should we use _icr < CCR to allow any risky CDP being liquidated?
        return _icr <= _tcr;
    }
}

File 8 of 29 : EbtcMath.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

library EbtcMath {
    uint256 internal constant DECIMAL_PRECISION = 1e18;
    uint256 public constant MAX_TCR = type(uint256).max;

    /* Precision for Nominal ICR (independent of price). Rationale for the value:
     *
     * - Making it “too high” could lead to overflows.
     * - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division.
     *
     * This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,
     * and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.
     *
     */
    uint256 internal constant NICR_PRECISION = 1e20;

    function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {
        return (_a < _b) ? _a : _b;
    }

    function _max(uint256 _a, uint256 _b) internal pure returns (uint256) {
        return (_a >= _b) ? _a : _b;
    }

    /**
     * credit to OpenZeppelin
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "EbtcMath: downcast to uint128 will overflow");
        return uint128(value);
    }

    /*
     * Multiply two decimal numbers and use normal rounding rules:
     * -round product up if 19'th mantissa digit >= 5
     * -round product down if 19'th mantissa digit < 5
     *
     * Used only inside the exponentiation, _decPow().
     */
    function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {
        uint256 prod_xy = x * y;

        decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
    }

    /*
     * _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.
     *
     * Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity.
     *
     * Called by two functions that represent time in units of minutes:
     * 1) CdpManager._calcDecayedBaseRate
     * 2) CommunityIssuance._getCumulativeIssuanceFraction
     *
     * The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals
     * "minutes in 1000 years": 60 * 24 * 365 * 1000
     *
     * If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be
     * negligibly different from just passing the cap, since:
     *
     * In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years
     * In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible
     */
    function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint256) {
        if (_minutes > 525600000) {
            _minutes = 525600000;
        } // cap to avoid overflow

        if (_minutes == 0) {
            return DECIMAL_PRECISION;
        }

        uint256 y = DECIMAL_PRECISION;
        uint256 x = _base;
        uint256 n = _minutes;

        // Exponentiation-by-squaring
        while (n > 1) {
            if (n % 2 == 0) {
                x = decMul(x, x);
                n = n / 2;
            } else {
                // if (n % 2 != 0)
                y = decMul(x, y);
                x = decMul(x, x);
                n = (n - 1) / 2;
            }
        }

        return decMul(x, y);
    }

    function _getAbsoluteDifference(uint256 _a, uint256 _b) internal pure returns (uint256) {
        return (_a >= _b) ? (_a - _b) : (_b - _a);
    }

    function _computeNominalCR(uint256 _collShares, uint256 _debt) internal pure returns (uint256) {
        if (_debt > 0) {
            return (_collShares * NICR_PRECISION) / _debt;
        }
        // Return the maximal value for uint256 if the Cdp has a debt of 0. Represents "infinite" CR.
        else {
            // if (_debt == 0)
            return MAX_TCR;
        }
    }

    /// @dev Compute collateralization ratio, given stETH balance, price, and debt balance
    function _computeCR(
        uint256 _stEthBalance,
        uint256 _debt,
        uint256 _price
    ) internal pure returns (uint256) {
        if (_debt > 0) {
            uint256 newCollRatio = (_stEthBalance * _price) / _debt;

            return newCollRatio;
        }
        // Return the maximal value for uint256 if the Cdp has a debt of 0. Represents "infinite" CR.
        else {
            // if (_debt == 0)
            return MAX_TCR;
        }
    }
}

File 9 of 29 : ICollateralToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "./IERC20.sol";

/**
 * Based on the stETH:
 *  -   https://docs.lido.fi/contracts/lido#
 */
interface ICollateralToken is IERC20 {
    // Returns the amount of shares that corresponds to _ethAmount protocol-controlled Ether
    function getSharesByPooledEth(uint256 _ethAmount) external view returns (uint256);

    // Returns the amount of Ether that corresponds to _sharesAmount token shares
    function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);

    // Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account.
    function transferShares(address _recipient, uint256 _sharesAmount) external returns (uint256);

    // Returns the amount of shares owned by _account
    function sharesOf(address _account) external view returns (uint256);

    // Returns authorized oracle address
    function getOracle() external view returns (address);
}

File 10 of 29 : ICollateralTokenOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/**
 * Based on the stETH:
 *  -   https://docs.lido.fi/contracts/lido#
 */
interface ICollateralTokenOracle {
    // Return beacon specification data.
    function getBeaconSpec()
        external
        view
        returns (
            uint64 epochsPerFrame,
            uint64 slotsPerEpoch,
            uint64 secondsPerSlot,
            uint64 genesisTime
        );
}

File 11 of 29 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

/**
 * Based on the OpenZeppelin IER20 interface:
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
 *
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);

    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 12 of 29 : IERC2612.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

/**
 * @dev Interface of the ERC2612 standard as defined in the EIP.
 *
 * Adds the {permit} method, which can be used to change one's
 * {IERC20-allowance} without having to send a transaction, by signing a
 * message. This allows users to spend tokens without having to hold Ether.
 *
 * See https://eips.ethereum.org/EIPS/eip-2612.
 *
 * Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/
 */
interface IERC2612 {
    /**
     * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,
     * given `owner`'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current ERC2612 nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases `owner`'s nonce by one. This
     * prevents a signature from being used multiple times.
     *
     * `owner` can limit the time a Permit is valid for by setting `deadline` to
     * a value in the near future. The deadline argument can be set to uint256(-1) to
     * create Permits that effectively never expire.
     */
    function nonces(address owner) external view returns (uint256);

    function version() external view returns (string memory);

    function permitTypeHash() external view returns (bytes32);

    function domainSeparator() external view returns (bytes32);
}

File 13 of 29 : Proxy.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)

pragma solidity 0.8.17;

/**
 * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
 * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
 * be specified by overriding the virtual {_implementation} function.
 *
 * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
 * different contract through the {_delegate} function.
 *
 * The success and return data of the delegated call will be returned back to the caller of the proxy.
 * @dev BadgerDAO: Simplified to the core delegation functionality, without any additional features.
 */
contract Proxy {
    /**
     * @dev Delegates the current call to `implementation`.
     *
     * This function does not return to its internal call site, it will return directly to the external caller.
     */
    function _delegate(address implementation) internal virtual {
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }
}

File 14 of 29 : ReentrancyGuard.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.17;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 internal constant OPEN = 1;
    uint256 internal constant LOCKED = 2;

    uint256 public locked = OPEN;

    modifier nonReentrant() virtual {
        require(locked == OPEN, "ReentrancyGuard: Reentrancy in nonReentrant call");

        locked = LOCKED;

        _;

        locked = OPEN;
    }
}

File 15 of 29 : IActivePool.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./IPool.sol";
import "./ITwapWeightedObserver.sol";

interface IActivePool is IPool, ITwapWeightedObserver {
    // --- Events ---
    event ActivePoolEBTCDebtUpdated(uint256 _EBTCDebt);
    event SystemCollSharesUpdated(uint256 _coll);
    event FeeRecipientClaimableCollSharesIncreased(uint256 _coll, uint256 _fee);
    event FeeRecipientClaimableCollSharesDecreased(uint256 _coll, uint256 _fee);
    event FlashLoanSuccess(
        address indexed _receiver,
        address indexed _token,
        uint256 _amount,
        uint256 _fee
    );
    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);

    // --- Functions ---
    function transferSystemCollShares(address _account, uint256 _amount) external;

    function increaseSystemCollShares(uint256 _value) external;

    function transferSystemCollSharesAndLiquidatorReward(
        address _account,
        uint256 _shares,
        uint256 _liquidatorRewardShares
    ) external;

    function allocateSystemCollSharesToFeeRecipient(uint256 _shares) external;

    function claimFeeRecipientCollShares(uint256 _shares) external;

    function feeRecipientAddress() external view returns (address);

    function getFeeRecipientClaimableCollShares() external view returns (uint256);
}

File 16 of 29 : IBaseTwapWeightedObserver.sol
// SPDX-License Identifier: MIT
pragma solidity 0.8.17;

interface IBaseTwapWeightedObserver {
    // NOTE: Packing manually is cheaper, but this is simpler to understand and follow
    struct PackedData {
        // Slot 0
        // Seconds in a year: 3.154e+7
        /// @dev Accumulator value recorded for TWAP Observer until last update
        uint128 observerCumuVal; // 3.154e+7 * 80 * 100e27 = 2.5232e+38 | log_2(100e27 * 3.154e+7 * 80) = 127.568522171
        /// @dev Accumulator for TWAP globally
        uint128 accumulator; // 3.154e+7 * 80 * 100e27 = 2.5232e+38 | log_2(100e27 * 3.154e+7 * 80) = 127.568522171
        // NOTE: We can further compress this slot but we will not be able to use only one (see u72 impl)
        /// So what's the point of making the code more complex?

        // Slot 1
        /// @dev last update timestamp for TWAP Observer
        uint64 lastObserved; // Thousands of Years, if we use relative time we can use u32 | Relative to deploy time (as immutable)
        /// @dev last update timestamp for TWAP global track(spot) value
        uint64 lastAccrued; // Thousands of years
        // Expect eBTC debt to never surpass 100e27, which is 100 BILLION eBTC
        // log_2(100e27) = 96.3359147517 | log_2(100e27 / 1e18) = 36.5412090438
        // We could use a u64
        /// @dev average value since last observe
        uint128 lastObservedAverage;
    }
}

File 17 of 29 : IBorrowerOperations.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;
import "./IPositionManagers.sol";

// Common interface for the Cdp Manager.
interface IBorrowerOperations is IPositionManagers {
    // --- Events ---

    event FeeRecipientAddressChanged(address indexed _feeRecipientAddress);
    event FlashLoanSuccess(
        address indexed _receiver,
        address indexed _token,
        uint256 _amount,
        uint256 _fee
    );

    // --- Functions ---

    function openCdp(
        uint256 _EBTCAmount,
        bytes32 _upperHint,
        bytes32 _lowerHint,
        uint256 _stEthBalance
    ) external returns (bytes32);

    function openCdpFor(
        uint _EBTCAmount,
        bytes32 _upperHint,
        bytes32 _lowerHint,
        uint _collAmount,
        address _borrower
    ) external returns (bytes32);

    function addColl(
        bytes32 _cdpId,
        bytes32 _upperHint,
        bytes32 _lowerHint,
        uint256 _stEthBalanceIncrease
    ) external;

    function withdrawColl(
        bytes32 _cdpId,
        uint256 _stEthBalanceDecrease,
        bytes32 _upperHint,
        bytes32 _lowerHint
    ) external;

    function withdrawDebt(
        bytes32 _cdpId,
        uint256 _amount,
        bytes32 _upperHint,
        bytes32 _lowerHint
    ) external;

    function repayDebt(
        bytes32 _cdpId,
        uint256 _amount,
        bytes32 _upperHint,
        bytes32 _lowerHint
    ) external;

    function closeCdp(bytes32 _cdpId) external;

    function adjustCdp(
        bytes32 _cdpId,
        uint256 _stEthBalanceDecrease,
        uint256 _debtChange,
        bool isDebtIncrease,
        bytes32 _upperHint,
        bytes32 _lowerHint
    ) external;

    function adjustCdpWithColl(
        bytes32 _cdpId,
        uint256 _stEthBalanceDecrease,
        uint256 _debtChange,
        bool isDebtIncrease,
        bytes32 _upperHint,
        bytes32 _lowerHint,
        uint256 _stEthBalanceIncrease
    ) external;

    function claimSurplusCollShares() external;

    function feeRecipientAddress() external view returns (address);
}

File 18 of 29 : ICdpManager.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./IEbtcBase.sol";
import "./ICdpManagerData.sol";

// Common interface for the Cdp Manager.
interface ICdpManager is IEbtcBase, ICdpManagerData {
    // --- Functions ---
    function liquidate(bytes32 _cdpId) external;

    function partiallyLiquidate(
        bytes32 _cdpId,
        uint256 _partialAmount,
        bytes32 _upperPartialHint,
        bytes32 _lowerPartialHint
    ) external;

    function batchLiquidateCdps(bytes32[] calldata _cdpArray) external;

    function redeemCollateral(
        uint256 _EBTCAmount,
        bytes32 _firstRedemptionHint,
        bytes32 _upperPartialRedemptionHint,
        bytes32 _lowerPartialRedemptionHint,
        uint256 _partialRedemptionHintNICR,
        uint256 _maxIterations,
        uint256 _maxFee
    ) external;

    function updateStakeAndTotalStakes(bytes32 _cdpId) external returns (uint256);

    function syncAccounting(bytes32 _cdpId) external;

    function closeCdp(bytes32 _cdpId, address _borrower, uint256 _debt, uint256 _coll) external;

    function getRedemptionRate() external view returns (uint256);

    function getRedemptionRateWithDecay() external view returns (uint256);

    function getRedemptionFeeWithDecay(uint256 _stETHToRedeem) external view returns (uint256);

    function getCdpStatus(bytes32 _cdpId) external view returns (uint256);

    function getCdpStake(bytes32 _cdpId) external view returns (uint256);

    function getCdpDebt(bytes32 _cdpId) external view returns (uint256);

    function getCdpCollShares(bytes32 _cdpId) external view returns (uint256);

    function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view returns (uint);

    function initializeCdp(
        bytes32 _cdpId,
        uint256 _debt,
        uint256 _coll,
        uint256 _liquidatorRewardShares,
        address _borrower
    ) external;

    function updateCdp(
        bytes32 _cdpId,
        address _borrower,
        uint256 _coll,
        uint256 _debt,
        uint256 _newColl,
        uint256 _newDebt
    ) external;

    function getCachedTCR(uint256 _price) external view returns (uint256);

    function checkRecoveryMode(uint256 _price) external view returns (bool);
}

File 19 of 29 : ICdpManagerData.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./ICollSurplusPool.sol";
import "./IEBTCToken.sol";
import "./ISortedCdps.sol";
import "./IActivePool.sol";
import "./IRecoveryModeGracePeriod.sol";
import "../Dependencies/ICollateralTokenOracle.sol";

// Common interface for the Cdp Manager.
interface ICdpManagerData is IRecoveryModeGracePeriod {
    // --- Events ---

    event StakingRewardSplitSet(uint256 _stakingRewardSplit);
    event RedemptionFeeFloorSet(uint256 _redemptionFeeFloor);
    event MinuteDecayFactorSet(uint256 _minuteDecayFactor);
    event BetaSet(uint256 _beta);
    event RedemptionsPaused(bool _paused);

    event Liquidation(uint256 _liquidatedDebt, uint256 _liquidatedColl, uint256 _liqReward);
    event Redemption(
        uint256 _debtToRedeemExpected,
        uint256 _debtToRedeemActual,
        uint256 _collSharesSent,
        uint256 _feeCollShares,
        address indexed _redeemer
    );
    event CdpUpdated(
        bytes32 indexed _cdpId,
        address indexed _borrower,
        address indexed _executor,
        uint256 _oldDebt,
        uint256 _oldCollShares,
        uint256 _debt,
        uint256 _collShares,
        uint256 _stake,
        CdpOperation _operation
    );
    event CdpLiquidated(
        bytes32 indexed _cdpId,
        address indexed _borrower,
        uint _debt,
        uint _collShares,
        CdpOperation _operation,
        address indexed _liquidator,
        uint _premiumToLiquidator
    );
    event CdpPartiallyLiquidated(
        bytes32 indexed _cdpId,
        address indexed _borrower,
        uint256 _debt,
        uint256 _collShares,
        CdpOperation operation,
        address indexed _liquidator,
        uint _premiumToLiquidator
    );
    event BaseRateUpdated(uint256 _baseRate);
    event LastRedemptionTimestampUpdated(uint256 _lastFeeOpTime);
    event TotalStakesUpdated(uint256 _newTotalStakes);
    event SystemSnapshotsUpdated(uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot);
    event SystemDebtRedistributionIndexUpdated(uint256 _systemDebtRedistributionIndex);
    event CdpDebtRedistributionIndexUpdated(bytes32 _cdpId, uint256 _cdpDebtRedistributionIndex);
    event CdpArrayIndexUpdated(bytes32 _cdpId, uint256 _newIndex);
    event StEthIndexUpdated(uint256 _oldIndex, uint256 _newIndex, uint256 _updTimestamp);
    event CollateralFeePerUnitUpdated(uint256 _oldPerUnit, uint256 _newPerUnit, uint256 _feeTaken);
    event CdpFeeSplitApplied(
        bytes32 _cdpId,
        uint256 _oldPerUnitCdp,
        uint256 _newPerUnitCdp,
        uint256 _collReduced,
        uint256 _collLeft
    );

    enum CdpOperation {
        openCdp,
        closeCdp,
        adjustCdp,
        syncAccounting,
        liquidateInNormalMode,
        liquidateInRecoveryMode,
        redeemCollateral,
        partiallyLiquidate,
        failedPartialRedemption
    }

    enum Status {
        nonExistent,
        active,
        closedByOwner,
        closedByLiquidation,
        closedByRedemption
    }

    // Store the necessary data for a cdp
    struct Cdp {
        uint256 debt;
        uint256 coll;
        uint256 stake;
        uint128 liquidatorRewardShares;
        Status status;
    }

    /*
     * --- Variable container structs for liquidations ---
     *
     * These structs are used to hold, return and assign variables inside the liquidation functions,
     * in order to avoid the error: "CompilerError: Stack too deep".
     **/

    struct CdpDebtAndCollShares {
        uint256 debt;
        uint256 collShares;
    }

    struct LiquidationLocals {
        bytes32 cdpId;
        uint256 partialAmount; // used only for partial liquidation, default 0 means full liquidation
        uint256 price;
        uint256 ICR;
        bytes32 upperPartialHint;
        bytes32 lowerPartialHint;
        bool recoveryModeAtStart;
        uint256 TCR;
        uint256 totalSurplusCollShares;
        uint256 totalCollSharesToSend;
        uint256 totalDebtToBurn;
        uint256 totalDebtToRedistribute;
        uint256 totalLiquidatorRewardCollShares;
    }

    struct LiquidationRecoveryModeLocals {
        uint256 entireSystemDebt;
        uint256 entireSystemColl;
        uint256 totalDebtToBurn;
        uint256 totalCollSharesToSend;
        uint256 totalSurplusCollShares;
        bytes32 cdpId;
        uint256 price;
        uint256 ICR;
        uint256 totalDebtToRedistribute;
        uint256 totalLiquidatorRewardCollShares;
    }

    struct LocalVariables_OuterLiquidationFunction {
        uint256 price;
        bool recoveryModeAtStart;
        uint256 liquidatedDebt;
        uint256 liquidatedColl;
    }

    struct LocalVariables_LiquidationSequence {
        uint256 i;
        uint256 ICR;
        bytes32 cdpId;
        bool backToNormalMode;
        uint256 entireSystemDebt;
        uint256 entireSystemColl;
        uint256 price;
        uint256 TCR;
    }

    struct SingleRedemptionInputs {
        bytes32 cdpId;
        uint256 maxEBTCamount;
        uint256 price;
        bytes32 upperPartialRedemptionHint;
        bytes32 lowerPartialRedemptionHint;
        uint256 partialRedemptionHintNICR;
    }

    struct LiquidationValues {
        uint256 entireCdpDebt;
        uint256 debtToBurn;
        uint256 totalCollToSendToLiquidator;
        uint256 debtToRedistribute;
        uint256 collSurplus;
        uint256 liquidatorCollSharesReward;
    }

    struct LiquidationTotals {
        uint256 totalDebtInSequence;
        uint256 totalDebtToBurn;
        uint256 totalCollToSendToLiquidator;
        uint256 totalDebtToRedistribute;
        uint256 totalCollSurplus;
        uint256 totalCollReward;
    }

    // --- Variable container structs for redemptions ---

    struct RedemptionTotals {
        uint256 remainingDebtToRedeem;
        uint256 debtToRedeem;
        uint256 collSharesDrawn;
        uint256 totalCollSharesSurplus;
        uint256 feeCollShares;
        uint256 collSharesToRedeemer;
        uint256 decayedBaseRate;
        uint256 price;
        uint256 systemDebtAtStart;
        uint256 twapSystemDebtAtStart;
        uint256 systemCollSharesAtStart;
        uint256 tcrAtStart;
    }

    struct SingleRedemptionValues {
        uint256 debtToRedeem;
        uint256 collSharesDrawn;
        uint256 collSurplus;
        uint256 liquidatorRewardShares;
        bool cancelledPartial;
        bool fullRedemption;
        uint256 newPartialNICR;
    }

    function getActiveCdpsCount() external view returns (uint256);

    function totalStakes() external view returns (uint256);

    function ebtcToken() external view returns (IEBTCToken);

    function systemStEthFeePerUnitIndex() external view returns (uint256);

    function systemStEthFeePerUnitIndexError() external view returns (uint256);

    function stEthIndex() external view returns (uint256);

    function calcFeeUponStakingReward(
        uint256 _newIndex,
        uint256 _prevIndex
    ) external view returns (uint256, uint256, uint256);

    function syncGlobalAccounting() external; // Accrues StEthFeeSplit without influencing Grace Period

    function syncGlobalAccountingAndGracePeriod() external; // Accrues StEthFeeSplit and influences Grace Period

    function getAccumulatedFeeSplitApplied(
        bytes32 _cdpId,
        uint256 _systemStEthFeePerUnitIndex
    ) external view returns (uint256, uint256);

    function getCachedNominalICR(bytes32 _cdpId) external view returns (uint256);

    function getCachedICR(bytes32 _cdpId, uint256 _price) external view returns (uint256);

    function getSyncedCdpDebt(bytes32 _cdpId) external view returns (uint256);

    function getSyncedCdpCollShares(bytes32 _cdpId) external view returns (uint256);

    function getSyncedICR(bytes32 _cdpId, uint256 _price) external view returns (uint256);

    function getSyncedTCR(uint256 _price) external view returns (uint256);

    function getSyncedSystemCollShares() external view returns (uint256);

    function getSyncedNominalICR(bytes32 _cdpId) external view returns (uint256);

    function getPendingRedistributedDebt(bytes32 _cdpId) external view returns (uint256);

    function hasPendingRedistributedDebt(bytes32 _cdpId) external view returns (bool);

    function getSyncedDebtAndCollShares(
        bytes32 _cdpId
    ) external view returns (uint256 debt, uint256 collShares);

    function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) external view returns (bool);

    function totalCollateralSnapshot() external view returns (uint256);

    function totalStakesSnapshot() external view returns (uint256);
}

File 20 of 29 : ICollSurplusPool.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

interface ICollSurplusPool {
    // --- Events ---

    event SurplusCollSharesAdded(
        bytes32 indexed _cdpId,
        address indexed _account,
        uint256 _claimableSurplusCollShares,
        uint256 _surplusCollSharesAddedFromCollateral,
        uint256 _surplusCollSharesAddedFromLiquidatorReward
    );
    event CollSharesTransferred(address indexed _to, uint256 _amount);

    event SweepTokenSuccess(address indexed _token, uint256 _amount, address indexed _recipient);

    // --- Contract setters ---

    function getTotalSurplusCollShares() external view returns (uint256);

    function getSurplusCollShares(address _account) external view returns (uint256);

    function increaseSurplusCollShares(
        bytes32 _cdpId,
        address _account,
        uint256 _collateralShares,
        uint256 _liquidatorRewardShares
    ) external;

    function claimSurplusCollShares(address _account) external;

    function increaseTotalSurplusCollShares(uint256 _value) external;
}

File 21 of 29 : IEbtcBase.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./IPriceFeed.sol";

interface IEbtcBase {
    function priceFeed() external view returns (IPriceFeed);
}

File 22 of 29 : IEBTCToken.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "../Dependencies/IERC20.sol";
import "../Dependencies/IERC2612.sol";

interface IEBTCToken is IERC20, IERC2612 {
    // --- Functions ---

    function mint(address _account, uint256 _amount) external;

    function burn(address _account, uint256 _amount) external;
}

File 23 of 29 : IPool.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

// Common interface for the Pools.
interface IPool {
    // --- Events ---

    event ETHBalanceUpdated(uint256 _newBalance);
    event EBTCBalanceUpdated(uint256 _newBalance);
    event CollSharesTransferred(address indexed _to, uint256 _amount);

    // --- Functions ---

    function getSystemCollShares() external view returns (uint256);

    function getSystemDebt() external view returns (uint256);

    function increaseSystemDebt(uint256 _amount) external;

    function decreaseSystemDebt(uint256 _amount) external;
}

File 24 of 29 : IPositionManagers.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

interface IPositionManagers {
    enum PositionManagerApproval {
        None,
        OneTime,
        Persistent
    }

    event PositionManagerApprovalSet(
        address indexed _borrower,
        address indexed _positionManager,
        PositionManagerApproval _approval
    );

    function getPositionManagerApproval(
        address _borrower,
        address _positionManager
    ) external view returns (PositionManagerApproval);

    function setPositionManagerApproval(
        address _positionManager,
        PositionManagerApproval _approval
    ) external;

    function revokePositionManagerApproval(address _positionManager) external;

    function renouncePositionManagerApproval(address _borrower) external;

    function permitPositionManagerApproval(
        address _borrower,
        address _positionManager,
        PositionManagerApproval _approval,
        uint _deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function version() external view returns (string memory);

    function permitTypeHash() external view returns (bytes32);

    function domainSeparator() external view returns (bytes32);
}

File 25 of 29 : IPriceFeed.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

interface IPriceFeed {
    // --- Events ---
    event LastGoodPriceUpdated(uint256 _lastGoodPrice);
    event PriceFeedStatusChanged(Status newStatus);
    event FallbackCallerChanged(
        address indexed _oldFallbackCaller,
        address indexed _newFallbackCaller
    );
    event UnhealthyFallbackCaller(address indexed _fallbackCaller, uint256 timestamp);
    event CollateralFeedSourceUpdated(address indexed stEthFeed);

    // --- Structs ---

    struct ChainlinkResponse {
        uint80 roundEthBtcId;
        uint80 roundStEthEthId;
        uint256 answer;
        uint256 timestampEthBtc;
        uint256 timestampStEthEth;
        bool success;
    }

    struct FallbackResponse {
        uint256 answer;
        uint256 timestamp;
        bool success;
    }

    // --- Enum ---

    enum Status {
        chainlinkWorking,
        usingFallbackChainlinkUntrusted,
        bothOraclesUntrusted,
        usingFallbackChainlinkFrozen,
        usingChainlinkFallbackUntrusted
    }

    // --- Function ---
    function fetchPrice() external returns (uint256);
}

File 26 of 29 : IRecoveryModeGracePeriod.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// Interface for State Updates that can trigger RM Liquidations
interface IRecoveryModeGracePeriod {
    event TCRNotified(uint256 TCR); /// NOTE: Mostly for debugging to ensure synch

    // NOTE: Ts is implicit in events (it's added by GETH)
    event GracePeriodStart();
    event GracePeriodEnd();
    event GracePeriodDurationSet(uint256 _recoveryModeGracePeriodDuration);

    function notifyStartGracePeriod(uint256 tcr) external;

    function notifyEndGracePeriod(uint256 tcr) external;
}

File 27 of 29 : ISortedCdps.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

// Common interface for the SortedCdps Doubly Linked List.
interface ISortedCdps {
    // --- Events ---

    event NodeAdded(bytes32 _id, uint _NICR);
    event NodeRemoved(bytes32 _id);

    // --- Functions ---

    function remove(bytes32 _id) external;

    function batchRemove(bytes32[] memory _ids) external;

    function reInsert(bytes32 _id, uint256 _newICR, bytes32 _prevId, bytes32 _nextId) external;

    function contains(bytes32 _id) external view returns (bool);

    function isFull() external view returns (bool);

    function isEmpty() external view returns (bool);

    function getSize() external view returns (uint256);

    function getMaxSize() external view returns (uint256);

    function getFirst() external view returns (bytes32);

    function getLast() external view returns (bytes32);

    function getNext(bytes32 _id) external view returns (bytes32);

    function getPrev(bytes32 _id) external view returns (bytes32);

    function validInsertPosition(
        uint256 _ICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external view returns (bool);

    function findInsertPosition(
        uint256 _ICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external view returns (bytes32, bytes32);

    function insert(
        address owner,
        uint256 _ICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external returns (bytes32);

    function getOwnerAddress(bytes32 _id) external pure returns (address);

    function nonExistId() external view returns (bytes32);

    function cdpCountOf(address owner) external view returns (uint256);

    function getCdpCountOf(
        address owner,
        bytes32 startNodeId,
        uint maxNodes
    ) external view returns (uint256, bytes32);

    function getCdpsOf(address owner) external view returns (bytes32[] memory);

    function getAllCdpsOf(
        address owner,
        bytes32 startNodeId,
        uint maxNodes
    ) external view returns (bytes32[] memory, uint256, bytes32);

    function cdpOfOwnerByIndex(address owner, uint256 index) external view returns (bytes32);

    function cdpOfOwnerByIdx(
        address owner,
        uint256 index,
        bytes32 startNodeId,
        uint maxNodes
    ) external view returns (bytes32, bool);

    function toCdpId(
        address owner,
        uint256 blockHeight,
        uint256 nonce
    ) external pure returns (bytes32);

    function nextCdpNonce() external view returns (uint256);
}

File 28 of 29 : ITwapWeightedObserver.sol
// SPDX-License Identifier: MIT
pragma solidity 0.8.17;
import {IBaseTwapWeightedObserver} from "./IBaseTwapWeightedObserver.sol";

interface ITwapWeightedObserver is IBaseTwapWeightedObserver {
    event TwapDisabled();

    function PERIOD() external view returns (uint256);

    function valueToTrack() external view returns (uint128);

    function timeToAccrue() external view returns (uint64);

    function getLatestAccumulator() external view returns (uint128);

    function observe() external returns (uint256);

    function update() external;

    function twapDisabled() external view returns (bool);
}

File 30 of 29 : SortedCdps.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "./Interfaces/ISortedCdps.sol";
import "./Interfaces/ICdpManager.sol";
import "./Interfaces/IBorrowerOperations.sol";

/*
 * A sorted doubly linked list with nodes sorted in descending order.
 *
 * Nodes map to active Cdps in the system by Id.
 * Nodes are ordered according to their current nominal individual collateral ratio (NICR),
 * which is like the ICR but without the price, i.e., just collateral / debt.
 *
 * The list optionally accepts insert position hints.
 *
 * NICRs are computed dynamically at runtime, and not stored on the Node. This is because NICRs of active Cdps
 * change dynamically as liquidation events occur.
 *
 * The list relies on the fact that liquidation events preserve ordering: a liquidation decreases the NICRs of all active Cdps,
 * but maintains their order. A node inserted based on current NICR will maintain the correct position,
 * relative to it's peers, as rewards accumulate, as long as it's raw collateral and debt have not changed.
 * Thus, Nodes remain sorted by current NICR.
 *
 * Nodes need only be re-inserted upon a CDP operation - when the owner adds or removes collateral or debt
 * to their position.
 *
 * The list is a modification of the following audited SortedDoublyLinkedList:
 * https://github.com/livepeer/protocol/blob/master/contracts/libraries/SortedDoublyLL.sol
 *
 *
 * Changes made in the Liquity implementation:
 *
 * - Keys have been removed from nodes
 *
 * - Ordering checks for insertion are performed by comparing an NICR argument to the current NICR, calculated at runtime.
 *   The list relies on the property that ordering by ICR is maintained as the stETH:BTC price varies.
 *
 * - Public functions with parameters have been made internal to save gas, and given an external wrapper function for external access
 *
 *
 * Changes made in the Ebtc implementation:
 *
 * - Positions are now indexed by Ids, not addresses. Functions to generate Ids are provided.
 *
 * - Added batchRemove functions to optimize redemptions.
 *
 * - Added more O(n) getter functions and pagination-flavor variants, intended for off-chain use.
 */
contract SortedCdps is ISortedCdps {
    string public constant NAME = "SortedCdps";

    address public immutable borrowerOperationsAddress;

    ICdpManager public immutable cdpManager;

    uint256 public immutable maxSize;

    uint256 constant ADDRESS_SHIFT = 96; // 8 * 12; Puts the address at leftmost bytes32 position
    uint256 constant BLOCK_SHIFT = 64; // 8 * 8; Puts the block value after the address

    // Information for a node in the list
    struct Node {
        bytes32 nextId; // Id of next node (smaller NICR) in the list
        bytes32 prevId; // Id of previous node (larger NICR) in the list
    }

    // Information for the list
    struct Data {
        bytes32 head; // Head of the list. Also the node in the list with the largest NICR
        bytes32 tail; // Tail of the list. Also the node in the list with the smallest NICR
        mapping(bytes32 => Node) nodes; // Track the corresponding ids for each node in the list
    }

    uint256 public size; // Current size of the list

    Data public data;

    uint256 public nextCdpNonce;
    bytes32 public constant dummyId =
        0x0000000000000000000000000000000000000000000000000000000000000000;

    /// @notice Constructor
    /// @dev Sets max list size
    /// @param _size Max number of nodes allowed in the list
    /// @param _cdpManagerAddress Address of CdpManager contract
    /// @param _borrowerOperationsAddress Address of BorrowerOperations contract
    constructor(uint256 _size, address _cdpManagerAddress, address _borrowerOperationsAddress) {
        if (_size == 0) {
            _size = type(uint256).max;
        }

        maxSize = _size;

        cdpManager = ICdpManager(_cdpManagerAddress);
        borrowerOperationsAddress = _borrowerOperationsAddress;
    }

    /// @notice Encodes a unique CDP Id from owner, block and nonce
    /// @dev Inspired https://github.com/balancer-labs/balancer-v2-monorepo/blob/18bd5fb5d87b451cc27fbd30b276d1fb2987b529/pkg/vault/contracts/PoolRegistry.sol
    /// @param owner Owner address of the CDP
    /// @param blockHeight Block number when CDP opened
    /// @param nonce Unique nonce for CDP
    /// @return Unique bytes32 CDP Id
    function toCdpId(
        address owner,
        uint256 blockHeight,
        uint256 nonce
    ) public pure returns (bytes32) {
        bytes32 serialized;

        serialized |= bytes32(nonce);
        serialized |= bytes32(blockHeight) << BLOCK_SHIFT; // to accommendate more than 4.2 billion blocks
        serialized |= bytes32(uint256(uint160(owner))) << ADDRESS_SHIFT;

        return serialized;
    }

    /// @notice Get owner address of a given CDP, given CdpId.
    /// @dev The owner address is stored in the first 20 bytes of the CdpId
    /// @param cdpId cdpId of CDP to get owner of
    /// @return owner address of the CDP
    function getOwnerAddress(bytes32 cdpId) public pure override returns (address) {
        uint256 _tmp = uint256(cdpId) >> ADDRESS_SHIFT;
        return address(uint160(_tmp));
    }

    /// @notice Get dummy non-existent CDP Id
    /// @return Dummy non-existent CDP Id
    function nonExistId() public pure override returns (bytes32) {
        return dummyId;
    }

    /// @notice Find a specific CDP for a given owner, indexed by it's place in the linked list relative to other Cdps owned by the same address
    /// @notice Reverts if the index exceeds the number of active Cdps owned by the given owner
    /// @dev Intended for off-chain use, O(n) operation on size of SortedCdps linked list
    /// @param owner address of CDP owner
    /// @param index index of CDP, ordered by position in linked list relative to Cdps of the same owner
    /// @return CDP Id if found
    function cdpOfOwnerByIndex(
        address owner,
        uint256 index
    ) external view override returns (bytes32) {
        (bytes32 _cdpId, ) = _cdpOfOwnerByIndex(owner, index, dummyId, 0);
        return _cdpId;
    }

    /// @dev a pagination-flavor search (from least ICR to biggest ICR) for CDP owned by given owner and specified index (starting at given CDP)
    /// @param owner address of CDP owner
    /// @param index index of CDP, ordered by position in linked list relative to Cdps of the same owner
    /// @param startNodeId the seach traversal will start at this given CDP instead of the tail of the list
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of Cdps
    /// @return CDP Id if found, else return last seen CDP
    /// @return True if CDP found, false otherwise
    function cdpOfOwnerByIdx(
        address owner,
        uint256 index,
        bytes32 startNodeId,
        uint maxNodes
    ) external view override returns (bytes32, bool) {
        return _cdpOfOwnerByIndex(owner, index, startNodeId, maxNodes);
    }

    /// @notice Get a user CDP by index using pagination
    /// @dev return EITHER the found CDP owned by given owner & index with a true indicator OR
    /// @dev current lastly-visited CDP as the startNode for next pagination with a false indicator
    /// @param owner Owner address to get CDP for
    /// @param index Index of CDP amongst user's Cdps
    /// @param startNodeId Start position CDP Id
    /// @param maxNodes Max number of Cdps to traverse
    /// @return cdpId The CDP Id if found, otherwise return current lastly-visited CDP as the startNode for next pagination
    /// @return found True if the CDP was found, false otherwise
    function _cdpOfOwnerByIndex(
        address owner,
        uint256 index,
        bytes32 startNodeId,
        uint maxNodes
    ) internal view returns (bytes32, bool) {
        // walk the list, until we get to the indexed CDP
        // start at the given node or from the tail of list
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
        uint _currentIndex = 0;
        uint i;

        while (_currentCdpId != dummyId) {
            // if the current CDP is owned by specified owner
            if (getOwnerAddress(_currentCdpId) == owner) {
                // if the current index of the owner CDP matches specified index
                if (_currentIndex == index) {
                    return (_currentCdpId, true);
                } else {
                    // if not, increment the owner index as we've seen a CDP owned by them
                    _currentIndex = _currentIndex + 1;
                }
            }
            ++i;

            // move to the next CDP in the list
            _currentCdpId = data.nodes[_currentCdpId].prevId;

            // cut the run if we exceed expected iterations through the loop
            if (maxNodes > 0 && i >= maxNodes) {
                break;
            }
        }
        // if we reach maximum iteration or end of list
        // without seeing the specified index for the owner
        // then maybe a new pagination is needed
        return (_currentCdpId, false);
    }

    /// @notice Get active CDP count for an owner address
    /// @dev Intended for off-chain use, O(n) operation on size of linked list
    /// @param owner Owner address to count Cdps for
    /// @return count Number of active Cdps owned by the address
    function cdpCountOf(address owner) external view override returns (uint256) {
        (uint256 _cnt, ) = _cdpCountOf(owner, dummyId, 0);
        return _cnt;
    }

    /// @notice a Pagination-flavor search for the count of Cdps owned by given owner
    /// @notice Starts from a given CdpId in the sorted list, and moves from lowest ICR to highest ICR
    /// @param startNodeId the count traversal will start at this given CDP instead of the tail of the list
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of Cdps
    /// @return count Number of active Cdps owned by the address in the segment of the list traversed
    /// @return last seen CDP for the startNode for next pagination
    function getCdpCountOf(
        address owner,
        bytes32 startNodeId,
        uint maxNodes
    ) external view override returns (uint256, bytes32) {
        return _cdpCountOf(owner, startNodeId, maxNodes);
    }

    /// @dev return the found CDP count owned by given owner with
    /// @dev current lastly-visited CDP as the startNode for next pagination
    function _cdpCountOf(
        address owner,
        bytes32 startNodeId,
        uint maxNodes
    ) internal view returns (uint256, bytes32) {
        // walk the list, until we get to the count
        // start at the given node or from the tail of list
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);
        uint _ownedCount = 0;
        uint i = 0;

        while (_currentCdpId != dummyId) {
            // if the current CDP is owned by specified owner
            if (getOwnerAddress(_currentCdpId) == owner) {
                _ownedCount = _ownedCount + 1;
            }
            ++i;

            // move to the next CDP in the list
            _currentCdpId = data.nodes[_currentCdpId].prevId;

            // cut the run if we exceed expected iterations through the loop
            if (maxNodes > 0 && i >= maxNodes) {
                break;
            }
        }
        return (_ownedCount, _currentCdpId);
    }

    /// @notice Get all active Cdps for a given address
    /// @dev Intended for off-chain use, O(n) operation on size of linked list
    /// @param owner address of CDP owner
    /// @return cdps all CdpIds of the specified owner
    function getCdpsOf(address owner) external view override returns (bytes32[] memory cdps) {
        // Naive method uses two-pass strategy to determine exactly how many Cdps are owned by owner
        // This roughly halves the amount of Cdps we can process before relying on pagination or off-chain methods
        (uint _ownedCount, ) = _cdpCountOf(owner, dummyId, 0);
        if (_ownedCount > 0) {
            (bytes32[] memory _allCdps, , ) = _getCdpsOf(owner, dummyId, 0, _ownedCount);
            cdps = _allCdps;
        }
    }

    /// @dev a pagination-flavor search retrieval of (from least ICR to biggest ICR) Cdps owned by given owner (starting at given CDP)
    /// @param startNodeId the traversal will start at this given CDP instead of the tail of the list
    /// @param maxNodes the traversal will go through the list by this given maximum limit of number of Cdps
    /// @return all CdpIds of the specified owner found by search starting at the specified startNodeId for the specified maximum iteration count
    /// @return found number of Cdp for the owner
    /// @return starting CdpId for next pagination within current SortedCdps
    function getAllCdpsOf(
        address owner,
        bytes32 startNodeId,
        uint maxNodes
    ) external view override returns (bytes32[] memory, uint256, bytes32) {
        // Naive method uses two-pass strategy to determine exactly how many Cdps are owned by owner
        // This roughly halves the amount of Cdps we can process before relying on pagination or off-chain methods
        (uint _ownedCount, ) = _cdpCountOf(owner, startNodeId, maxNodes);
        return _getCdpsOf(owner, startNodeId, maxNodes, _ownedCount);
    }

    /// @dev return EITHER the found Cdps (also the count) owned by given owner OR empty array with
    /// @dev current lastly-visited CDP as the startNode for next pagination
    function _getCdpsOf(
        address owner,
        bytes32 startNodeId,
        uint maxNodes,
        uint maxArraySize
    ) internal view returns (bytes32[] memory, uint256, bytes32) {
        if (maxArraySize == 0) {
            return (new bytes32[](0), 0, dummyId);
        }

        // Two-pass strategy, halving the amount of Cdps we can process before relying on pagination or off-chain methods
        bytes32[] memory userCdps = new bytes32[](maxArraySize);
        uint i = 0;
        uint _cdpRetrieved;

        // walk the list, until we get to the index
        // start at the given node or from the tail of list
        bytes32 _currentCdpId = (startNodeId == dummyId ? data.tail : startNodeId);

        while (_currentCdpId != dummyId) {
            // if the current CDP is owned by specified owner
            if (getOwnerAddress(_currentCdpId) == owner) {
                userCdps[_cdpRetrieved] = _currentCdpId;
                ++_cdpRetrieved;
            }
            ++i;

            // move to the next CDP in the list
            _currentCdpId = data.nodes[_currentCdpId].prevId;

            // cut the run if we exceed expected iterations through the loop
            if (maxNodes > 0 && i >= maxNodes) {
                break;
            }
        }

        return (userCdps, _cdpRetrieved, _currentCdpId);
    }

    /// @notice Add a node to the list
    /// @param owner CDP owner for corresponding Id
    /// @param _NICR Node's NICR
    /// @param _prevId Id of previous node for the insert position
    /// @param _nextId Id of next node for the insert position
    /// @return _id Id of the new node
    function insert(
        address owner,
        uint256 _NICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external override returns (bytes32) {
        _requireCallerIsBOorCdpM();
        bytes32 _id = toCdpId(owner, block.number, nextCdpNonce);
        require(cdpManager.getCdpStatus(_id) == 0, "SortedCdps: new id is NOT nonExistent!");

        _insert(_id, _NICR, _prevId, _nextId);

        unchecked {
            ++nextCdpNonce;
        }

        return _id;
    }

    function _insert(bytes32 _id, uint256 _NICR, bytes32 _prevId, bytes32 _nextId) internal {
        // List must not be full
        require(!isFull(), "SortedCdps: List is full");
        // List must not already contain node
        require(!contains(_id), "SortedCdps: List already contains the node");
        // Node id must not be null
        require(_id != dummyId, "SortedCdps: Id cannot be zero");
        // NICR must be non-zero
        require(_NICR > 0, "SortedCdps: NICR must be positive");

        bytes32 prevId = _prevId;
        bytes32 nextId = _nextId;

        if (!_validInsertPosition(_NICR, prevId, nextId)) {
            // Sender's hint was not a valid insert position
            // Use sender's hint to find a valid insert position
            (prevId, nextId) = _findInsertPosition(_NICR, prevId, nextId);
        }

        if (prevId == dummyId && nextId == dummyId) {
            // Insert as head and tail
            data.head = _id;
            data.tail = _id;
        } else if (prevId == dummyId) {
            // Insert before `prevId` as the head
            data.nodes[_id].nextId = data.head;
            data.nodes[data.head].prevId = _id;
            data.head = _id;
        } else if (nextId == dummyId) {
            // Insert after `nextId` as the tail
            data.nodes[_id].prevId = data.tail;
            data.nodes[data.tail].nextId = _id;
            data.tail = _id;
        } else {
            // Insert at insert position between `prevId` and `nextId`
            data.nodes[_id].nextId = nextId;
            data.nodes[_id].prevId = prevId;
            data.nodes[prevId].nextId = _id;
            data.nodes[nextId].prevId = _id;
        }

        size = size + 1;
        emit NodeAdded(_id, _NICR);
    }

    /// @notice Remove a node from the sorted list, by Id
    /// @param _id The CdpId to be removed
    function remove(bytes32 _id) external override {
        _requireCallerIsCdpManager();
        _remove(_id);
    }

    /// @notice Batch a node from the sorted list, by Id
    /// @notice Strong trust assumption that the specified nodes are sorted in the same order as in the input array
    /// @dev Optimization to reduce gas cost for removing multiple nodes on redemption
    /// @param _ids Array of CdpIds to remove
    function batchRemove(bytes32[] memory _ids) external override {
        _requireCallerIsCdpManager();
        uint256 _len = _ids.length;
        require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");

        bytes32 _firstPrev = data.nodes[_ids[0]].prevId;
        bytes32 _lastNext = data.nodes[_ids[_len - 1]].nextId;

        require(
            _firstPrev != dummyId || _lastNext != dummyId,
            "SortedCdps: batchRemove() leave ZERO node left!"
        );

        for (uint256 i = 0; i < _len; ++i) {
            require(contains(_ids[i]), "SortedCdps: List does not contain the id");
        }

        // orphan nodes in between to save gas
        if (_firstPrev != dummyId) {
            data.nodes[_firstPrev].nextId = _lastNext;
        } else {
            data.head = _lastNext;
        }
        if (_lastNext != dummyId) {
            data.nodes[_lastNext].prevId = _firstPrev;
        } else {
            data.tail = _firstPrev;
        }

        // delete node & owner storages to get gas refund
        for (uint i = 0; i < _len; ++i) {
            delete data.nodes[_ids[i]];
            emit NodeRemoved(_ids[i]);
        }
        size = size - _len;
    }

    function _remove(bytes32 _id) internal {
        // List must contain the node
        require(contains(_id), "SortedCdps: List does not contain the id");

        if (size > 1) {
            // List contains more than a single node
            if (_id == data.head) {
                // The removed node is the head
                // Set head to next node
                data.head = data.nodes[_id].nextId;
                // Set prev pointer of new head to null
                data.nodes[data.head].prevId = dummyId;
            } else if (_id == data.tail) {
                // The removed node is the tail
                // Set tail to previous node
                data.tail = data.nodes[_id].prevId;
                // Set next pointer of new tail to null
                data.nodes[data.tail].nextId = dummyId;
            } else {
                // The removed node is neither the head nor the tail
                // Set next pointer of previous node to the next node
                data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;
                // Set prev pointer of next node to the previous node
                data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;
            }
        } else {
            // List contains a single node
            // Set the head and tail to null
            data.head = dummyId;
            data.tail = dummyId;
        }

        delete data.nodes[_id];
        size = size - 1;
        emit NodeRemoved(_id);
    }

    /// @notice Re-insert an existing node at a new position, based on its new NICR
    /// @param _id Node's id
    /// @param _newNICR Node's new NICR
    /// @param _prevId Id of previous node for the new insert position
    /// @param _nextId Id of next node for the new insert position
    function reInsert(
        bytes32 _id,
        uint256 _newNICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external override {
        _requireCallerIsBOorCdpM();
        // List must contain the node
        require(contains(_id), "SortedCdps: List does not contain the id");
        // NICR must be non-zero
        require(_newNICR > 0, "SortedCdps: NICR must be positive");

        // Remove node from the list
        _remove(_id);

        _insert(_id, _newNICR, _prevId, _nextId);
    }

    /// @dev Checks if the list contains a given node Id
    /// @param _id The Id of the node
    /// @return true if the node exists, false otherwise
    function contains(bytes32 _id) public view override returns (bool) {
        bool _exist = _id != dummyId && (data.head == _id || data.tail == _id);
        if (!_exist) {
            Node memory _node = data.nodes[_id];
            _exist = _id != dummyId && (_node.nextId != dummyId && _node.prevId != dummyId);
        }
        return _exist;
    }

    /// @dev Checks if the list is full
    /// @return true if the list is full, false otherwise
    function isFull() public view override returns (bool) {
        return size == maxSize;
    }

    /// @dev Checks if the list is empty
    /// @return true if the list is empty, false otherwise
    function isEmpty() public view override returns (bool) {
        return size == 0;
    }

    /// @dev Returns the current size of the list
    /// @return The current size of the list
    function getSize() external view override returns (uint256) {
        return size;
    }

    /// @dev Returns the maximum size of the list
    /// @return The maximum size of the list
    function getMaxSize() external view override returns (uint256) {
        return maxSize;
    }

    /// @dev Returns the first node in the list (node with the largest NICR)
    /// @return The Id of the first node
    function getFirst() external view override returns (bytes32) {
        return data.head;
    }

    /// @dev Returns the last node in the list (node with the smallest NICR)
    /// @return The Id of the last node
    function getLast() external view override returns (bytes32) {
        return data.tail;
    }

    /// @dev Returns the next node (with a smaller NICR) in the list for a given node
    /// @param _id The Id of the node
    /// @return The Id of the next node
    function getNext(bytes32 _id) external view override returns (bytes32) {
        return data.nodes[_id].nextId;
    }

    /// @dev Returns the previous node (with a larger NICR) in the list for a given node
    /// @param _id The Id of the node
    /// @return The Id of the previous node
    function getPrev(bytes32 _id) external view override returns (bytes32) {
        return data.nodes[_id].prevId;
    }

    /// @dev Check if a pair of nodes is a valid insertion point for a new node with the given NICR
    /// @param _NICR Node's NICR
    /// @param _prevId Id of previous node for the insert position
    /// @param _nextId Id of next node for the insert position
    /// @return true if the position is valid, false otherwise
    function validInsertPosition(
        uint256 _NICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external view override returns (bool) {
        return _validInsertPosition(_NICR, _prevId, _nextId);
    }

    function _validInsertPosition(
        uint256 _NICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) internal view returns (bool) {
        if (_prevId == dummyId && _nextId == dummyId) {
            // `(null, null)` is a valid insert position if the list is empty
            return isEmpty();
        } else if (_prevId == dummyId) {
            // `(null, _nextId)` is a valid insert position if `_nextId` is the head of the list
            return data.head == _nextId && _NICR >= cdpManager.getCachedNominalICR(_nextId);
        } else if (_nextId == dummyId) {
            // `(_prevId, null)` is a valid insert position if `_prevId` is the tail of the list
            return data.tail == _prevId && _NICR <= cdpManager.getCachedNominalICR(_prevId);
        } else {
            // `(_prevId, _nextId)` is a valid insert position if they are adjacent nodes and `_NICR` falls between the two nodes' NICRs
            return
                data.nodes[_prevId].nextId == _nextId &&
                cdpManager.getCachedNominalICR(_prevId) >= _NICR &&
                _NICR >= cdpManager.getCachedNominalICR(_nextId);
        }
    }

    /// @dev Descend the list (larger NICRs to smaller NICRs) to find a valid insert position
    /// @param _NICR Node's NICR
    /// @param _startId Id of node to start descending the list from
    /// @return The previous node Id for the inserted node
    /// @return The next node Id for the inserted node
    function _descendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {
        // If `_startId` is the head, check if the insert position is before the head
        if (data.head == _startId && _NICR >= cdpManager.getCachedNominalICR(_startId)) {
            return (dummyId, _startId);
        }

        bytes32 prevId = _startId;
        bytes32 nextId = data.nodes[prevId].nextId;

        // Descend the list until we reach the end or until we find a valid insert position
        while (prevId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) {
            prevId = data.nodes[prevId].nextId;
            nextId = data.nodes[prevId].nextId;
        }

        return (prevId, nextId);
    }

    /// @dev Ascend the list (smaller NICRs to larger NICRs) to find a valid insert position
    /// @param _NICR Node's NICR
    /// @param _startId Id of node to start ascending the list from
    /// @return The previous node Id for the inserted node
    /// @return The next node Id for the inserted node
    function _ascendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {
        // If `_startId` is the tail, check if the insert position is after the tail
        if (data.tail == _startId && _NICR <= cdpManager.getCachedNominalICR(_startId)) {
            return (_startId, dummyId);
        }

        bytes32 nextId = _startId;
        bytes32 prevId = data.nodes[nextId].prevId;

        // Ascend the list until we reach the end or until we find a valid insertion point
        while (nextId != dummyId && !_validInsertPosition(_NICR, prevId, nextId)) {
            nextId = data.nodes[nextId].prevId;
            prevId = data.nodes[nextId].prevId;
        }

        return (prevId, nextId);
    }

    /// @dev Find the insert position for a node with the given NICR
    /// @param _NICR Node's NICR
    /// @param _prevId Id of previous node for the insert position
    /// @param _nextId Id of next node for the insert position
    /// @return The previous node Id for the inserted node
    /// @return The next node Id for the inserted node
    function findInsertPosition(
        uint256 _NICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) external view override returns (bytes32, bytes32) {
        return _findInsertPosition(_NICR, _prevId, _nextId);
    }

    function _findInsertPosition(
        uint256 _NICR,
        bytes32 _prevId,
        bytes32 _nextId
    ) internal view returns (bytes32, bytes32) {
        bytes32 prevId = _prevId;
        bytes32 nextId = _nextId;

        if (prevId != dummyId) {
            if (!contains(prevId) || _NICR > cdpManager.getCachedNominalICR(prevId)) {
                // `prevId` does not exist anymore or now has a smaller NICR than the given NICR
                prevId = dummyId;
            }
        }

        if (nextId != dummyId) {
            if (!contains(nextId) || _NICR < cdpManager.getCachedNominalICR(nextId)) {
                // `nextId` does not exist anymore or now has a larger NICR than the given NICR
                nextId = dummyId;
            }
        }

        if (prevId == dummyId && nextId == dummyId) {
            // No hint - descend list starting from head
            return _descendList(_NICR, data.head);
        } else if (prevId == dummyId) {
            // No `prevId` for hint - ascend list starting from `nextId`
            return _ascendList(_NICR, nextId);
        } else if (nextId == dummyId) {
            // No `nextId` for hint - descend list starting from `prevId`
            return _descendList(_NICR, prevId);
        } else {
            // Descend list starting from `prevId`
            return _descendList(_NICR, prevId);
        }
    }

    // === Modifiers ===

    /// @dev Asserts that the caller of the function is the CdpManager
    function _requireCallerIsCdpManager() internal view {
        require(msg.sender == address(cdpManager), "SortedCdps: Caller is not the CdpManager");
    }

    /// @dev Asserts that the caller of the function is either the BorrowerOperations contract or the CdpManager
    function _requireCallerIsBOorCdpM() internal view {
        require(
            msg.sender == borrowerOperationsAddress || msg.sender == address(cdpManager),
            "SortedCdps: Caller is neither BO nor CdpM"
        );
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract ABI

[{"inputs":[{"internalType":"contract CdpManager","name":"_cdpManager","type":"address"},{"internalType":"contract ISortedCdps","name":"_sortedCdps","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"cdpManager","outputs":[{"internalType":"contract CdpManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"_startIdx","type":"int256"},{"internalType":"uint256","name":"_count","type":"uint256"}],"name":"getMultipleSortedCdps","outputs":[{"components":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"debt","type":"uint256"},{"internalType":"uint256","name":"coll","type":"uint256"},{"internalType":"uint256","name":"stake","type":"uint256"},{"internalType":"uint256","name":"snapshotEBTCDebt","type":"uint256"}],"internalType":"struct MultiCdpGetter.CombinedCdpData[]","name":"_cdps","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sortedCdps","outputs":[{"internalType":"contract ISortedCdps","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106100415760003560e01c80631ac9299414610046578063bb038e151461006f578063c526a6b0146100ae575b600080fd5b610059610054366004610b94565b6100d5565b6040516100669190610bb6565b60405180910390f35b6100967f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b3681565b6040516001600160a01b039091168152602001610066565b6100967f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf81565b6060600080600085126100ed57508390506001610108565b6100f8856001610c3a565b61010190610c5a565b9150600090505b60007f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b031663de8fa4316040518163ffffffff1660e01b8152600401602060405180830381865afa158015610168573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061018c9190610c76565b90508083106101fb5760408051600080825260208201909252906101f3565b6101e06040518060a0016040528060008019168152602001600081526020016000815260200160008152602001600081525090565b8152602001906001900390816101ab5790505b50935061023b565b60006102078483610ca5565b905080861115610215578095505b821561022c576102258487610244565b9450610239565b61023684876106f0565b94505b505b50505092915050565b606060007f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b0316631e2231436040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102ca9190610c76565b905060005b84811015610372576040516394dfe33f60e01b8152600481018390527f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b0316906394dfe33f90602401602060405180830381865afa15801561033c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103609190610c76565b915061036b81610cbe565b90506102cf565b508267ffffffffffffffff81111561038c5761038c610c8f565b6040519080825280602002602001820160405280156103f257816020015b6103df6040518060a0016040528060008019168152602001600081526020016000815260200160008152602001600081525090565b8152602001906001900390816103aa5790505b50915060005b838110156106e8578183828151811061041357610413610cd7565b60209081029190910101515260405163a33aacfd60e01b8152600481018390527f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b366001600160a01b03169063a33aacfd9060240160a060405180830381865afa158015610484573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104a89190610ced565b50508551909250859150839081106104c2576104c2610cd7565b60209081029190910101516060015260405163a60b578d60e01b8152600481018390527f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b366001600160a01b03169063a60b578d906024016040805180830381865afa158015610535573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105599190610d5d565b84838151811061056b5761056b610cd7565b602002602001015160200185848151811061058857610588610cd7565b6020026020010151604001828152508281525050507f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b366001600160a01b03166317f43b86836040518263ffffffff1660e01b81526004016105eb91815260200190565b602060405180830381865afa158015610608573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062c9190610c76565b83828151811061063e5761063e610cd7565b6020908102919091010151608001526040516394dfe33f60e01b8152600481018390527f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b0316906394dfe33f90602401602060405180830381865afa1580156106b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106d69190610c76565b91506106e181610cbe565b90506103f8565b505092915050565b606060007f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b0316634d6228316040518163ffffffff1660e01b8152600401602060405180830381865afa158015610752573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107769190610c76565b905060005b8481101561081e57604051630d0117ab60e31b8152600481018390527f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b031690636808bd5890602401602060405180830381865afa1580156107e8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061080c9190610c76565b915061081781610cbe565b905061077b565b508267ffffffffffffffff81111561083857610838610c8f565b60405190808252806020026020018201604052801561089e57816020015b61088b6040518060a0016040528060008019168152602001600081526020016000815260200160008152602001600081525090565b8152602001906001900390816108565790505b50915060005b838110156106e857818382815181106108bf576108bf610cd7565b60209081029190910101515260405163a33aacfd60e01b8152600481018390527f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b366001600160a01b03169063a33aacfd9060240160a060405180830381865afa158015610930573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109549190610ced565b505085519092508591508390811061096e5761096e610cd7565b60209081029190910101516060015260405163a60b578d60e01b8152600481018390527f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b366001600160a01b03169063a60b578d906024016040805180830381865afa1580156109e1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a059190610d5d565b848381518110610a1757610a17610cd7565b6020026020010151602001858481518110610a3457610a34610cd7565b6020026020010151604001828152508281525050507f000000000000000000000000e89a0378237c21b94126f77ed5fa0fdd2ede2b366001600160a01b03166317f43b86836040518263ffffffff1660e01b8152600401610a9791815260200190565b602060405180830381865afa158015610ab4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ad89190610c76565b838281518110610aea57610aea610cd7565b602090810291909101015160800152604051630d0117ab60e31b8152600481018390527f000000000000000000000000bc79539670992a4e1948dadb737472a2fad7cecf6001600160a01b031690636808bd5890602401602060405180830381865afa158015610b5e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b829190610c76565b9150610b8d81610cbe565b90506108a4565b60008060408385031215610ba757600080fd5b50508035926020909101359150565b602080825282518282018190526000919060409081850190868401855b82811015610c175781518051855286810151878601528581015186860152606080820151908601526080908101519085015260a09093019290850190600101610bd3565b5091979650505050505050565b634e487b7160e01b600052601160045260246000fd5b80820182811260008312801582168215821617156106e8576106e8610c24565b6000600160ff1b8201610c6f57610c6f610c24565b5060000390565b600060208284031215610c8857600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b81810381811115610cb857610cb8610c24565b92915050565b600060018201610cd057610cd0610c24565b5060010190565b634e487b7160e01b600052603260045260246000fd5b600080600080600060a08688031215610d0557600080fd5b85519450602086015193506040860151925060608601516fffffffffffffffffffffffffffffffff81168114610d3a57600080fd5b608087015190925060058110610d4f57600080fd5b809150509295509295909350565b60008060408385031215610d7057600080fd5b50508051602090910151909290915056fea26469706673582212200fcb04568ddd6f99cfa465e3bd37d16147b486433f446e80b068869e49fca17964736f6c63430008110033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.