YieldBearingStablecoinPriceFeed

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/PriceFeeds/yieldBearingStablecoinPriceFeed.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import "./MainnetPriceFeedBase.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";


contract YieldBearingStablecoinPriceFeed is MainnetPriceFeedBase {
    uint256 public constant UNDERLYING_USD_DEVIATION_THRESHOLD = 2e16; // 2%
    IERC4626 public immutable yieldBearingToken;

    constructor(
        address _underlyingUsdOracleAddress, // Underlying asset/USD oracle
        address _yieldBearingToken, // ERC4626 yield-bearing token address
        uint256 _underlyingUsdStalenessThreshold,
        address _borrowerOperationsAddress
    ) MainnetPriceFeedBase(_underlyingUsdOracleAddress, _underlyingUsdStalenessThreshold, _borrowerOperationsAddress) {

        yieldBearingToken = IERC4626(_yieldBearingToken);

        _fetchPricePrimary(false);

        // Check the oracle didn't already fail
        assert(priceSource == PriceSource.primary);
    }

    function fetchPrice() public returns (uint256, bool) {
        return _fetchPricePrimary(false);
    }

    function fetchRedemptionPrice() external returns (uint256, bool) {
        return _fetchPricePrimary(true);
    }

    function _fetchPricePrimary(bool _isRedemption) internal returns (uint256, bool) {
        // If using last good price due to oracle failure, return it
        if (priceSource == PriceSource.lastGoodPrice) {
            return (lastGoodPrice, false);
        }

        // Get underlying asset/USD price (returns in 18 decimals from _getOracleAnswer)
        (uint256 underlyingUsdPrice, bool underlyingUsdOracleDown) = _getOracleAnswer(ethUsdOracle);
        if (underlyingUsdOracleDown) {
            return (_shutDownAndSwitchToLastGoodPrice(address(ethUsdOracle.aggregator)), true);
        }

        // Get canonical rate from ERC4626 yield-bearing token (18 decimals)
        uint256 yieldBearingRate = yieldBearingToken.convertToAssets(1e18);
        uint256 canonicalPrice = underlyingUsdPrice * yieldBearingRate / 1e18;

        uint256 price = canonicalPrice;

        lastGoodPrice = price;
        return (price, false);
    }

    function _withinDeviationThreshold(uint256 _priceToCheck, uint256 _referencePrice, uint256 _deviationThreshold)
        internal
        pure
        returns (bool)
    {
        uint256 max = _referencePrice * (DECIMAL_PRECISION + _deviationThreshold) / DECIMAL_PRECISION;
        uint256 min = _referencePrice * (DECIMAL_PRECISION - _deviationThreshold) / DECIMAL_PRECISION;
        return _priceToCheck >= min && _priceToCheck <= max;
    }
}


"
    },
    "src/PriceFeeds/MainnetPriceFeedBase.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import "../Dependencies/Ownable.sol";
import "../Dependencies/AggregatorV3Interface.sol";
import "../Interfaces/IMainnetPriceFeed.sol";
import "../BorrowerOperations.sol";

abstract contract MainnetPriceFeedBase is IMainnetPriceFeed {
    // Determines where the PriceFeed sources data from. Possible states:
    // - primary: Uses the primary price calcuation, which depends on the specific feed
    // - ETHUSDxCanonical: Uses Chainlink's ETH-USD multiplied by the LST' canonical rate
    // - lastGoodPrice: the last good price recorded by this PriceFeed.
    PriceSource public priceSource;

    // Last good price tracker for the derived USD price
    uint256 public lastGoodPrice;

    struct Oracle {
        AggregatorV3Interface aggregator;
        uint256 stalenessThreshold;
        uint8 decimals;
    }

    struct ChainlinkResponse {
        uint80 roundId;
        int256 answer;
        uint256 timestamp;
        bool success;
    }

    error InsufficientGasForExternalCall();

    event ShutDownFromOracleFailure(address _failedOracleAddr);

    Oracle public ethUsdOracle;

    IBorrowerOperations borrowerOperations;

    constructor(address _ethUsdOracleAddress, uint256 _ethUsdStalenessThreshold, address _borrowOperationsAddress) {
        // Store ETH-USD oracle
        ethUsdOracle.aggregator = AggregatorV3Interface(_ethUsdOracleAddress);
        ethUsdOracle.stalenessThreshold = _ethUsdStalenessThreshold;
        ethUsdOracle.decimals = ethUsdOracle.aggregator.decimals();

        borrowerOperations = IBorrowerOperations(_borrowOperationsAddress);

        assert(ethUsdOracle.decimals == 8);
    }

    function _getOracleAnswer(Oracle memory _oracle) internal view returns (uint256, bool) {
        ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse(_oracle.aggregator);

        uint256 scaledPrice;
        bool oracleIsDown;
        // Check oracle is serving an up-to-date and sensible price. If not, shut down this collateral branch.
        if (!_isValidChainlinkPrice(chainlinkResponse, _oracle.stalenessThreshold)) {
            oracleIsDown = true;
        } else {
            scaledPrice = _scaleChainlinkPriceTo18decimals(chainlinkResponse.answer, _oracle.decimals);
        }

        return (scaledPrice, oracleIsDown);
    }

    function _shutDownAndSwitchToLastGoodPrice(address _failedOracleAddr) internal returns (uint256) {
        // Shut down the branch
        borrowerOperations.shutdownFromOracleFailure();

        priceSource = PriceSource.lastGoodPrice;

        emit ShutDownFromOracleFailure(_failedOracleAddr);
        return lastGoodPrice;
    }

    function _getCurrentChainlinkResponse(AggregatorV3Interface _aggregator)
        internal
        view
        returns (ChainlinkResponse memory chainlinkResponse)
    {
        uint256 gasBefore = gasleft();

        // Try to get latest price data:
        try _aggregator.latestRoundData() returns (
            uint80 roundId, int256 answer, uint256, /* startedAt */ uint256 updatedAt, uint80 /* answeredInRound */
        ) {
            // If call to Chainlink succeeds, return the response and success = true
            chainlinkResponse.roundId = roundId;
            chainlinkResponse.answer = answer;
            chainlinkResponse.timestamp = updatedAt;
            chainlinkResponse.success = true;

            return chainlinkResponse;
        } catch {
            // Require that enough gas was provided to prevent an OOG revert in the call to Chainlink
            // causing a shutdown. Instead, just revert. Slightly conservative, as it includes gas used
            // in the check itself.
            if (gasleft() <= gasBefore / 64) revert InsufficientGasForExternalCall();

            // If call to Chainlink aggregator reverts, return a zero response with success = false
            return chainlinkResponse;
        }
    }

    // False if:
    // - Call to Chainlink aggregator reverts
    // - price is too stale, i.e. older than the oracle's staleness threshold
    // - Price answer is 0 or negative
    function _isValidChainlinkPrice(ChainlinkResponse memory chainlinkResponse, uint256 _stalenessThreshold)
        internal
        view
        returns (bool)
    {
        return chainlinkResponse.success && block.timestamp - chainlinkResponse.timestamp < _stalenessThreshold
            && chainlinkResponse.answer > 0;
    }

    // Trust assumption: Chainlink won't change the decimal precision on any feed used in v2 after deployment
    function _scaleChainlinkPriceTo18decimals(int256 _price, uint256 _decimals) internal pure returns (uint256) {
        // Scale an int price to a uint with 18 decimals
        return uint256(_price) * 10 ** (18 - _decimals);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/IERC20.sol";
import "../token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 *
 * _Available since v4.7._
 */
interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   withdraw execution, and are accounted for during withdraw.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
     * through a redeem call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
     *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
     *   same transaction.
     * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
     *   redemption would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   redeem execution, and are accounted for during redeem.
     * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
    },
    "src/Dependencies/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

/**
 * Based on OpenZeppelin's Ownable contract:
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
 *
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting `initialOwner` as the initial owner.
     */
    constructor(address initialOwner) {
        _owner = initialOwner;
        emit OwnershipTransferred(address(0), initialOwner);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Returns true if the caller is the current owner.
     */
    function isOwner() public view returns (bool) {
        return msg.sender == _owner;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     *
     * NOTE: This function is not safe, as it doesn’t check owner is calling it.
     * Make sure you check it before calling it.
     */
    function _renounceOwnership() internal {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }
}
"
    },
    "src/Dependencies/AggregatorV3Interface.sol": {
      "content": "// SPDX-License-Identifier: MIT
// Code from https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol

pragma solidity 0.8.24;

interface AggregatorV3Interface {
    function decimals() external view returns (uint8);
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
"
    },
    "src/Interfaces/IMainnetPriceFeed.sol": {
      "content": "// SPDX-License-Identifier: MIT
import "../Interfaces/IPriceFeed.sol";
import "../Dependencies/AggregatorV3Interface.sol";

pragma solidity ^0.8.0;

interface IMainnetPriceFeed is IPriceFeed {
    enum PriceSource {
        primary,
        ETHUSDxCanonical,
        lastGoodPrice
    }

    function ethUsdOracle() external view returns (AggregatorV3Interface, uint256, uint8);
    function priceSource() external view returns (PriceSource);
}
"
    },
    "src/BorrowerOperations.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.24;

import "./Dependencies/LiquityBase.sol";
import "./Dependencies/AddRemoveManagers.sol";
import "./Interfaces/IBorrowerOperations.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "./Interfaces/IAddressesRegistry.sol";
import "./Interfaces/ITroveManager.sol";
import "./Interfaces/IBoldToken.sol";
import "./Interfaces/ICollSurplusPool.sol";
import "./Interfaces/ISortedTroves.sol";
import "./Types/LatestTroveData.sol";
import "./Types/LatestBatchData.sol";
import {IEbisuBorrowerOperationsHelper} from "./Interfaces/IEbisuBorrowerOperationsHelper.sol";
import {IEbisuBranchManager} from "./Interfaces/IEbisuBranchManager.sol";
import "./Dependencies/EbisuUpgradeable.sol";

contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperations, EbisuUpgradeable {
    using SafeERC20 for IERC20;

    // --- Connected contract declarations ---

    IERC20 internal collToken;
    ITroveManager internal troveManager;
    address internal gasPoolAddress;
    ICollSurplusPool internal collSurplusPool;
    IBoldToken internal boldToken;
    IEbisuBorrowerOperationsHelper public boHelper;
    IEbisuBranchManager internal branchManager;
    // A doubly linked list of Troves, sorted by their collateral ratios
    ISortedTroves internal sortedTroves;
    // Wrapped ETH for liquidation reserve (gas compensation)
    IWETH internal WETH;

    // Shutdown system collateral ratio. If the system's total collateral ratio (TCR) for a given collateral falls below the SCR,
    // the protocol triggers the shutdown of the borrow market and permanently disables all borrowing operations except for closing Troves.
    bool public hasBeenShutDown;

    // Extra buffer of collateral ratio to join a batch or adjust a trove inside a batch (on top of MCR)
    uint256 private BCR;

    uint256 private minDebt;

    /*
     * Mapping from TroveId to granted address for interest rate setting (batch manager).
     *
     * Batch managers set the interest rate for every Trove in the batch. The interest rate is the same for all Troves in the batch.
     */
    mapping(uint256 => address) public interestBatchManagerOf;

    // Reserved for future upgrades
    uint256[50] private __gap;

    /* --- Variable container structs  ---

    Used to hold, return and assign variables inside a function, in order to avoid the error:
    "CompilerError: Stack too deep". */

    struct OpenTroveVars {
        ITroveManager troveManager;
        uint256 troveId;
        TroveChange change;
        LatestBatchData batch;
    }

    struct LocalVariables_removeFromBatch {
        ITroveManager troveManager;
        ISortedTroves sortedTroves;
        address batchManager;
        LatestTroveData trove;
        LatestBatchData batch;
        uint256 batchFutureDebt;
        TroveChange batchChange;
    }

    error IsShutDown();
    error TCRNotBelowSCR();
    error TroveExists();
    error TroveNotOpen();
    error TroveNotActive();
    error TroveNotZombie();
    error TroveWithZeroDebt();
    error UpfrontFeeTooHigh();
    error ICRBelowMCR();
    error ICRBelowMCRPlusBCR();
    error TCRBelowCCR();
    error DebtBelowMin();
    error NewOracleFailureDetected();
    error TroveNotInBatch();
    error CallerNotTroveManager();
    error CallerNotPriceFeed();
    error MintingDebtPaused();
    error DebtCapReached();
    error BatchSharesRatioTooLow();
    error CallerNotBorrowerOperationsHelper();
    error CallerNotBranchManager();
    event ShutDown(uint256 _tcr);

    constructor() {
        _disableInitializers();
    }

    //TODO: remove virtual on prod
    function initialize(address _addressesRegistryAddress)
        public
        virtual
        override(AddRemoveManagers, LiquityBase, IBorrowerOperations)
        initializer
    {
        IAddressesRegistry _addressesRegistry = IAddressesRegistry(_addressesRegistryAddress);
        LiquityBase.initialize(_addressesRegistryAddress);
        AddRemoveManagers.initialize(_addressesRegistryAddress);
        _ebisuAdminRegistry = IEbisuAdminRegistry(address(_addressesRegistry.collateralRegistry()));
  
        collToken = _addressesRegistry.collToken();
        WETH = _addressesRegistry.WETH();

        troveManager = _addressesRegistry.troveManager();
        gasPoolAddress = _addressesRegistry.gasPoolAddress();
        collSurplusPool = _addressesRegistry.collSurplusPool();
        sortedTroves = _addressesRegistry.sortedTroves();
        boldToken = _addressesRegistry.boldToken();
        boHelper = IEbisuBorrowerOperationsHelper(_addressesRegistry.borrowerOperationsHelper());
        branchManager = _addressesRegistry.branchManager();
        collToken.approve(address(activePool), type(uint256).max);
        BCR = _addressesRegistry.BCR();
        minDebt = MIN_DEBT;
        // This makes impossible to open a trove with zero withdrawn Bold
        assert(minDebt > 0);
    }

    // --- Borrower Trove Operations ---

    function openTrove(
        address _owner,
        uint256 _ownerIndex,
        uint256 _collAmount,
        uint256 _boldAmount,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _annualInterestRate,
        uint256 _maxUpfrontFee,
        address _addManager,
        address _removeManager,
        address _receiver
    ) external virtual override returns (uint256) {
        branchManager.requireValidAnnualInterestRate(_annualInterestRate);

        OpenTroveVars memory vars;

        vars.troveId = _openTrove(
            _owner,
            _ownerIndex,
            _collAmount,
            _boldAmount,
            _annualInterestRate,
            address(0),
            0,
            0,
            _maxUpfrontFee,
            _addManager,
            _removeManager,
            _receiver,
            vars.change
        );

        // Set the stored Trove properties and mint the NFT
        troveManager.onOpenTrove(_owner, vars.troveId, vars.change, _annualInterestRate);

        sortedTroves.insert(vars.troveId, _annualInterestRate, _upperHint, _lowerHint);

        return vars.troveId;
    }

    function openTroveAndJoinInterestBatchManager(OpenTroveAndJoinInterestBatchManagerParams calldata _params)
        external
        virtual
        override
        returns (uint256)
    {
        boHelper.requireValidInterestBatchManager(_params.interestBatchManager);

        OpenTroveVars memory vars;
        vars.troveManager = troveManager;

        vars.batch = vars.troveManager.getLatestBatchData(_params.interestBatchManager);

        // We set old weighted values here, as it's only necessary for batches, so we don't need to pass them to _openTrove func
        vars.change.batchAccruedManagementFee = vars.batch.accruedManagementFee;
        vars.change.oldWeightedRecordedDebt = vars.batch.weightedRecordedDebt;
        vars.change.oldWeightedRecordedBatchManagementFee = vars.batch.weightedRecordedBatchManagementFee;
        vars.troveId = _openTrove(
            _params.owner,
            _params.ownerIndex,
            _params.collAmount,
            _params.boldAmount,
            vars.batch.annualInterestRate,
            _params.interestBatchManager,
            vars.batch.entireDebtWithoutRedistribution,
            vars.batch.annualManagementFee,
            _params.maxUpfrontFee,
            _params.addManager,
            _params.removeManager,
            _params.receiver,
            vars.change
        );

        interestBatchManagerOf[vars.troveId] = _params.interestBatchManager;

        // Set the stored Trove properties and mint the NFT
        vars.troveManager.onOpenTroveAndJoinBatch(
            _params.owner,
            vars.troveId,
            vars.change,
            _params.interestBatchManager,
            vars.batch.entireCollWithoutRedistribution,
            vars.batch.entireDebtWithoutRedistribution
        );

        sortedTroves.insertIntoBatch(
            vars.troveId,
            BatchId.wrap(_params.interestBatchManager),
            vars.batch.annualInterestRate,
            _params.upperHint,
            _params.lowerHint
        );

        return vars.troveId;
    }

    function _requireMintingDebtAllowed() internal view virtual {
        if (!branchManager.mintingDebtAllowed()) {
            revert MintingDebtPaused();
        }
    }

    function _openTrove(
        address _owner,
        uint256 _ownerIndex,
        uint256 _collAmount,
        uint256 _boldAmount,
        uint256 _annualInterestRate,
        address _interestBatchManager,
        uint256 _batchEntireDebt,
        uint256 _batchManagementAnnualFee,
        uint256 _maxUpfrontFee,
        address _addManager,
        address _removeManager,
        address _receiver,
        TroveChange memory _change
    ) internal virtual returns (uint256) {
        _requireIsNotShutDown();
        // verify branch is not paused
        _requireMintingDebtAllowed();
        LocalVariables_openTrove memory vars;

        // stack too deep not allowing to reuse troveManager from outer functions
        vars.troveManager = troveManager;
        vars.activePool = activePool;
        vars.boldToken = boldToken;

        vars.price = _requireOraclesLive();

        // --- Checks ---

        vars.troveId = uint256(keccak256(abi.encode(msg.sender, _owner, _ownerIndex)));
        _requireTroveDoesNotExists(vars.troveManager, vars.troveId);

        _change.collIncrease = _collAmount;
        _change.debtIncrease = _boldAmount;

        // For simplicity, we ignore the fee when calculating the approx. interest rate
        _change.newWeightedRecordedDebt = (_batchEntireDebt + _change.debtIncrease) * _annualInterestRate;

        vars.avgInterestRate = vars.activePool.getNewApproxAvgInterestRateFromTroveChange(_change);
        _change.upfrontFee = _calcUpfrontFee(_change.debtIncrease, vars.avgInterestRate);
        _requireUserAcceptsUpfrontFee(_change.upfrontFee, _maxUpfrontFee);

        vars.entireDebt = _change.debtIncrease + _change.upfrontFee;

        if (vars.entireDebt < minDebt) {
            revert DebtBelowMin();
        }

        vars.ICR = LiquityMath._computeCR(_collAmount, vars.entireDebt, vars.price);

        // Recalculate newWeightedRecordedDebt, now taking into account the upfront fee, and the batch fee if needed
        if (_interestBatchManager == address(0)) {
            _change.newWeightedRecordedDebt = vars.entireDebt * _annualInterestRate;

            // ICR is based on the requested Bold amount + upfront fee.
            _requireICRisAboveMCR(vars.ICR);
        } else {
            // old values have been set outside, before calling this function
            _change.newWeightedRecordedDebt = (_batchEntireDebt + vars.entireDebt) * _annualInterestRate;
            _change.newWeightedRecordedBatchManagementFee =
                (_batchEntireDebt + vars.entireDebt) * _batchManagementAnnualFee;
            // ICR is based on the requested Bold amount + upfront fee.
            // Troves in a batch have a stronger requirement (MCR+BCR)
            _requireICRisAboveMCRPlusBCR(vars.ICR);
        }

        vars.newTCR = _getNewTCRFromTroveChange(_change, vars.price);
        _requireNewTCRisAboveCCR(vars.newTCR);

        // --- Effects & interactions ---

        // Set add/remove managers
        _setAddManager(vars.troveId, _addManager);
        _setRemoveManagerAndReceiver(vars.troveId, _removeManager, _receiver);

        vars.activePool.mintAggInterestAndAccountForTroveChange(_change, _interestBatchManager);

        // Pull coll tokens from sender and move them to the Active Pool
        _pullCollAndSendToActivePool(vars.activePool, _collAmount);

        // Mint the requested _boldAmount to the borrower and mint the gas comp to the GasPool
        vars.boldToken.mint(msg.sender, _boldAmount);
        WETH.transferFrom(msg.sender, gasPoolAddress, ETH_GAS_COMPENSATION);

        _requireTotalDebtBelowCap();
        return vars.troveId;
    }

    // Send collateral to a trove
    function addColl(uint256 _troveId, uint256 _collAmount) external virtual override {
        ITroveManager troveManagerCached = troveManager;
        _requireTroveIsActive(troveManagerCached, _troveId);

        TroveChange memory troveChange;
        troveChange.collIncrease = _collAmount;

        _adjustTrove(
            troveManagerCached,
            _troveId,
            troveChange,
            0 // _maxUpfrontFee
        );
    }

    // Withdraw collateral from a trove
    function withdrawColl(uint256 _troveId, uint256 _collWithdrawal) external virtual override {
        ITroveManager troveManagerCached = troveManager;
        _requireTroveIsActive(troveManagerCached, _troveId);

        TroveChange memory troveChange;
        troveChange.collDecrease = _collWithdrawal;

        _adjustTrove(
            troveManagerCached,
            _troveId,
            troveChange,
            0 // _maxUpfrontFee
        );
    }

    // Withdraw Bold tokens from a trove: mint new Bold tokens to the owner, and increase the trove's debt accordingly
    function withdrawBold(uint256 _troveId, uint256 _boldAmount, uint256 _maxUpfrontFee) external virtual override {
        ITroveManager troveManagerCached = troveManager;
        _requireTroveIsActive(troveManagerCached, _troveId);

        TroveChange memory troveChange;
        troveChange.debtIncrease = _boldAmount;
        _adjustTrove(troveManagerCached, _troveId, troveChange, _maxUpfrontFee);
    }

    // Repay Bold tokens to a Trove: Burn the repaid Bold tokens, and reduce the trove's debt accordingly
    function repayBold(uint256 _troveId, uint256 _boldAmount) external virtual override {
        ITroveManager troveManagerCached = troveManager;
        _requireTroveIsActive(troveManagerCached, _troveId);

        TroveChange memory troveChange;
        troveChange.debtDecrease = _boldAmount;

        _adjustTrove(
            troveManagerCached,
            _troveId,
            troveChange,
            0 // _maxUpfrontFee
        );
    }

    function _initTroveChange(
        TroveChange memory _troveChange,
        uint256 _collChange,
        bool _isCollIncrease,
        uint256 _boldChange,
        bool _isDebtIncrease
    ) internal pure virtual {
        if (_isCollIncrease) {
            _troveChange.collIncrease = _collChange;
        } else {
            _troveChange.collDecrease = _collChange;
        }

        if (_isDebtIncrease) {
            _troveChange.debtIncrease = _boldChange;
        } else {
            _troveChange.debtDecrease = _boldChange;
        }
    }

    function adjustTrove(
        uint256 _troveId,
        uint256 _collChange,
        bool _isCollIncrease,
        uint256 _boldChange,
        bool _isDebtIncrease,
        uint256 _maxUpfrontFee
    ) external virtual override {
        ITroveManager troveManagerCached = troveManager;
        _requireTroveIsActive(troveManagerCached, _troveId);

        TroveChange memory troveChange;
        _initTroveChange(troveChange, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease);
        _adjustTrove(troveManagerCached, _troveId, troveChange, _maxUpfrontFee);
    }

    function adjustZombieTrove(
        uint256 _troveId,
        uint256 _collChange,
        bool _isCollIncrease,
        uint256 _boldChange,
        bool _isDebtIncrease,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) external virtual override {
        ITroveManager troveManagerCached = troveManager;
        _requireTroveIsZombie(troveManagerCached, _troveId);

        TroveChange memory troveChange;
        _initTroveChange(troveChange, _collChange, _isCollIncrease, _boldChange, _isDebtIncrease);
        _adjustTrove(troveManagerCached, _troveId, troveChange, _maxUpfrontFee);

        troveManagerCached.setTroveStatusToActive(_troveId);

        address batchManager = interestBatchManagerOf[_troveId];
        uint256 batchAnnualInterestRate;
        if (batchManager != address(0)) {
            LatestBatchData memory batch = troveManagerCached.getLatestBatchData(batchManager);
            batchAnnualInterestRate = batch.annualInterestRate;
        }
        _reInsertIntoSortedTroves(
            _troveId,
            troveManagerCached.getTroveAnnualInterestRate(_troveId),
            _upperHint,
            _lowerHint,
            batchManager,
            batchAnnualInterestRate
        );
    }

    function adjustTroveInterestRate(
        uint256 _troveId,
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) external virtual {
        _requireIsNotShutDown();

        ITroveManager troveManagerCached = troveManager;

        (TroveChange memory troveChange, LatestTroveData memory trove, uint256 newDebt) = boHelper
            .initAdjustTroveInterestRate(
            msg.sender, _troveId, _newAnnualInterestRate, _maxUpfrontFee, _upperHint, _lowerHint
        );

        troveManagerCached.onAdjustTroveInterestRate(
            _troveId, trove.entireColl, newDebt, _newAnnualInterestRate, troveChange
        );
    }

    /*
    * _adjustTrove(): Alongside a debt change, this function can perform either a collateral top-up or a collateral withdrawal.
    */
    function _adjustTrove(
        ITroveManager _troveManager,
        uint256 _troveId,
        TroveChange memory _troveChange,
        uint256 _maxUpfrontFee
    ) internal virtual {
        _requireIsNotShutDown();

        LocalVariables_adjustTrove memory vars;
        vars.activePool = activePool;
        vars.boldToken = boldToken;

        vars.price = _requireOraclesLive();
        vars.isBelowCriticalThreshold = _checkBelowCriticalThreshold(vars.price, branchManager.CCR());

        // --- Checks ---

        _requireTroveIsOpen(_troveManager, _troveId);

        address owner = troveNFT.ownerOf(_troveId);
        address receiver = owner; // If it’s a withdrawal, and remove manager privilege is set, a different receiver can be defined

        if (_troveChange.collDecrease > 0 || _troveChange.debtIncrease > 0) {
            receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, owner);
        } else {
            // RemoveManager assumes AddManager, so if the former is set, there's no need to check the latter
            _requireSenderIsOwnerOrAddManager(_troveId, owner);
            // No need to check the type of trove change for two reasons:
            // - If the check above fails, it means sender is not owner, nor AddManager, nor RemoveManager.
            //   An independent 3rd party should not be allowed here.
            // - If it's not collIncrease or debtDecrease, _requireNonZeroAdjustment would revert
        }

        (vars, _troveChange) = boHelper.initAdjustTrove(
            _troveId, msg.sender, vars, _troveChange, _maxUpfrontFee, interestBatchManagerOf[_troveId]
        );

        // --- Effects and interactions ---

        // vars.activePool.mintAggInterestAndAccountForTroveChange(_troveChange, batchManager);
        _moveTokensFromAdjustment(receiver, _troveChange, vars.boldToken, vars.activePool);
    }

    function closeTrove(uint256 _troveId) external virtual override {
        // --- Checks ---
        address receiver = _requireSenderIsOwnerOrRemoveManagerAndGetReceiver(_troveId, troveNFT.ownerOf(_troveId));
        address batchManager = interestBatchManagerOf[_troveId];
        LatestTroveData memory trove = boHelper.initCloseTrove(msg.sender, _troveId, batchManager);

        // Return ETH gas compensation
        WETH.transferFrom(gasPoolAddress, receiver, ETH_GAS_COMPENSATION);
        // Burn the remainder of the Trove's entire debt from the user
        boldToken.burn(msg.sender, trove.entireDebt);
        // Send the collateral back to the user
        activePool.sendColl(receiver, trove.entireColl);

        _wipeTroveMappings(_troveId);
    }

    function applyPendingDebt(uint256 _troveId, uint256 _lowerHint, uint256 _upperHint) public virtual {
        _requireIsNotShutDown();

        ITroveManager troveManagerCached = troveManager;

        _requireTroveIsOpen(troveManagerCached, _troveId);

        LatestTroveData memory trove = troveManagerCached.getLatestTroveData(_troveId);
        _requireNonZeroDebt(trove.entireDebt);

        TroveChange memory change;
        change.appliedRedistBoldDebtGain = trove.redistBoldDebtGain;
        change.appliedRedistCollGain = trove.redistCollGain;

        address batchManager = interestBatchManagerOf[_troveId];
        LatestBatchData memory batch;

        if (batchManager == address(0)) {
            change.oldWeightedRecordedDebt = trove.weightedRecordedDebt;
            change.newWeightedRecordedDebt = trove.entireDebt * trove.annualInterestRate;
        } else {
            batch = troveManagerCached.getLatestBatchData(batchManager);
            change.batchAccruedManagementFee = batch.accruedManagementFee;
            change.oldWeightedRecordedDebt = batch.weightedRecordedDebt;
            change.newWeightedRecordedDebt =
                (batch.entireDebtWithoutRedistribution + trove.redistBoldDebtGain) * batch.annualInterestRate;
            change.oldWeightedRecordedBatchManagementFee = batch.weightedRecordedBatchManagementFee;
            change.newWeightedRecordedBatchManagementFee =
                (batch.entireDebtWithoutRedistribution + trove.redistBoldDebtGain) * batch.annualManagementFee;
        }

        troveManagerCached.onApplyTroveInterest(
            _troveId,
            trove.entireColl,
            trove.entireDebt,
            batchManager,
            batch.entireCollWithoutRedistribution,
            batch.entireDebtWithoutRedistribution,
            change
        );
        activePool.mintAggInterestAndAccountForTroveChange(change, batchManager);

        // If the trove was zombie, and now it's not anymore, put it back in the list
        if (_checkTroveIsZombie(troveManagerCached, _troveId) && trove.entireDebt >= minDebt) {
            troveManagerCached.setTroveStatusToActive(_troveId);
            _reInsertIntoSortedTroves(
                _troveId, trove.annualInterestRate, _upperHint, _lowerHint, batchManager, batch.annualInterestRate
            );
        }
    }

    function setInterestIndividualDelegate(
        uint256 _troveId,
        address _delegate,
        uint128 _minInterestRate,
        uint128 _maxInterestRate,
        // only needed if trove was previously in a batch:
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee,
        uint256 _minInterestRateChangePeriod
    ) external virtual {
        _requireIsNotShutDown();

        boHelper.initSetInterestIndividualDelegate(
            msg.sender, _troveId, _delegate, _minInterestRate, _maxInterestRate, _minInterestRateChangePeriod
        );

        // Can't have both individual delegation and batch manager
        if (interestBatchManagerOf[_troveId] != address(0)) {
            // Not needed, implicitly checked in removeFromBatch
            //_requireValidAnnualInterestRate(_newAnnualInterestRate);
            removeFromBatch(_troveId, _newAnnualInterestRate, _upperHint, _lowerHint, _maxUpfrontFee);
        }
    }

    function lowerBatchManagementFee(uint256 _newAnnualManagementFee) external virtual {
        boHelper.lowerBatchManagementFee(msg.sender, _newAnnualManagementFee);
    }

    function kickFromBatch(uint256 _troveId, uint256 _upperHint, uint256 _lowerHint) external virtual override {
        _removeFromBatch({
            _troveId: _troveId,
            _newAnnualInterestRate: 0, // ignored when kicking
            _upperHint: _upperHint,
            _lowerHint: _lowerHint,
            _maxUpfrontFee: 0, // will use the batch's existing interest rate, so no fee
            _kick: true
        });
    }

    function removeFromBatch(
        uint256 _troveId,
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) public virtual override {
        _removeFromBatch({
            _troveId: _troveId,
            _newAnnualInterestRate: _newAnnualInterestRate,
            _upperHint: _upperHint,
            _lowerHint: _lowerHint,
            _maxUpfrontFee: _maxUpfrontFee,
            _kick: false
        });
    }

    function _removeFromBatch(
        uint256 _troveId,
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee,
        bool _kick
    ) internal virtual {
        _requireIsNotShutDown();

        LocalVariables_removeFromBatch memory vars;
        vars.troveManager = troveManager;
        vars.sortedTroves = sortedTroves;

        if (_kick) {
            _requireTroveIsOpen(vars.troveManager, _troveId);
        } else {
            _requireTroveIsActive(vars.troveManager, _troveId);
            _requireCallerIsBorrower(_troveId);
            branchManager.requireValidAnnualInterestRate(_newAnnualInterestRate);
        }

        vars.batchManager = _requireIsInBatch(_troveId);
        vars.trove = vars.troveManager.getLatestTroveData(_troveId);
        vars.batch = vars.troveManager.getLatestBatchData(vars.batchManager);

        if (_kick) {
            if (vars.batch.totalDebtShares * MAX_BATCH_SHARES_RATIO >= vars.batch.entireDebtWithoutRedistribution) {
                revert BatchSharesRatioTooLow();
            }
            _newAnnualInterestRate = vars.batch.annualInterestRate;
        }

        delete interestBatchManagerOf[_troveId];

        if (!_checkTroveIsZombie(vars.troveManager, _troveId)) {
            // Remove trove from Batch in SortedTroves
            vars.sortedTroves.removeFromBatch(_troveId);
            // Reinsert as single trove
            vars.sortedTroves.insert(_troveId, _newAnnualInterestRate, _upperHint, _lowerHint);
        }

        vars.batchFutureDebt =
            vars.batch.entireDebtWithoutRedistribution - (vars.trove.entireDebt - vars.trove.redistBoldDebtGain);

        vars.batchChange.appliedRedistBoldDebtGain = vars.trove.redistBoldDebtGain;
        vars.batchChange.appliedRedistCollGain = vars.trove.redistCollGain;
        vars.batchChange.batchAccruedManagementFee = vars.batch.accruedManagementFee;
        vars.batchChange.oldWeightedRecordedDebt = vars.batch.weightedRecordedDebt;
        vars.batchChange.newWeightedRecordedDebt =
            vars.batchFutureDebt * vars.batch.annualInterestRate + vars.trove.entireDebt * _newAnnualInterestRate;

        // Apply upfront fee on premature adjustments. It checks the resulting ICR
        if (
            vars.batch.annualInterestRate != _newAnnualInterestRate
                && block.timestamp < vars.trove.lastInterestRateAdjTime + INTEREST_RATE_ADJ_COOLDOWN
        ) {
            (vars.trove.entireDebt, vars.batchChange) = boHelper.applyUpfrontFee(
                vars.trove.entireColl, vars.trove.entireDebt, vars.batchChange, _maxUpfrontFee, false
            );
        }

        // Recalculate newWeightedRecordedDebt, now taking into account the upfront fee
        vars.batchChange.newWeightedRecordedDebt =
            vars.batchFutureDebt * vars.batch.annualInterestRate + vars.trove.entireDebt * _newAnnualInterestRate;
        // Add batch fees
        vars.batchChange.oldWeightedRecordedBatchManagementFee = vars.batch.weightedRecordedBatchManagementFee;
        vars.batchChange.newWeightedRecordedBatchManagementFee = vars.batchFutureDebt * vars.batch.annualManagementFee;

        activePool.mintAggInterestAndAccountForTroveChange(vars.batchChange, vars.batchManager);

        vars.troveManager.onRemoveFromBatch(
            _troveId,
            vars.trove.entireColl,
            vars.trove.entireDebt,
            vars.batchChange,
            vars.batchManager,
            vars.batch.entireCollWithoutRedistribution,
            vars.batch.entireDebtWithoutRedistribution,
            _newAnnualInterestRate
        );
    }

    function switchBatchManager(
        uint256 _troveId,
        uint256 _removeUpperHint,
        uint256 _removeLowerHint,
        address _newBatchManager,
        uint256 _addUpperHint,
        uint256 _addLowerHint,
        uint256 _maxUpfrontFee
    ) external virtual override {
        LatestBatchData memory oldBatch = boHelper.initSwitchBatchManager(_troveId, _newBatchManager);

        removeFromBatch(_troveId, oldBatch.annualInterestRate, _removeUpperHint, _removeLowerHint, 0);
        boHelper.setInterestBatchManager(
            msg.sender, _troveId, _newBatchManager, _addUpperHint, _addLowerHint, _maxUpfrontFee
        );
    }

    function _calcUpfrontFee(uint256 _debt, uint256 _avgInterestRate) internal pure virtual returns (uint256) {
        return _calcInterest(_debt * _avgInterestRate, UPFRONT_INTEREST_PERIOD);
    }

    // Call from TM to clean state here
    function onLiquidateTrove(uint256 _troveId) external virtual {
        _requireCallerIsTroveManager();

        _wipeTroveMappings(_troveId);
    }

    function _requireCallerIsTroveManager() internal view virtual {
        if (msg.sender != address(troveManager)) {
            revert CallerNotTroveManager();
        }
    }

    function _wipeTroveMappings(uint256 _troveId) internal virtual {
        boHelper.removeInterestIndividualDelegate(_troveId);
        delete interestBatchManagerOf[_troveId];
        _wipeAddRemoveManagers(_troveId);
    }

    /**
     * Claim remaining collateral from a liquidation with ICR exceeding the liquidation penalty
     */
    function claimCollateral() external virtual override {
        // send coll from CollSurplus Pool to owner
        collSurplusPool.claimColl(msg.sender);
    }

    function shutdown() external virtual {
        if (hasBeenShutDown) revert IsShutDown();

        uint256 totalColl = getEntireBranchColl();
        uint256 totalDebt = getEntireBranchDebt();
        (uint256 price, bool newOracleFailureDetected) = priceFeed.fetchPrice();
        // If the oracle failed, the above call to PriceFeed will have shut this branch down
        if (newOracleFailureDetected) return;

        // Otherwise, proceed with the TCR check:
        uint256 TCR = LiquityMath._computeCR(totalColl, totalDebt, price);
        if (TCR >= branchManager.SCR()) revert TCRNotBelowSCR();

        _applyShutdown();

        emit ShutDown(TCR);
    }

    // Not technically a "Borrower op", but seems best placed here given current shutdown logic.
    function shutdownFromOracleFailure() external virtual {
        _requireCallerIsPriceFeed();

        // No-op rather than revert here, so that the outer function call which fetches the price does not revert
        // if the system is already shut down.
        if (hasBeenShutDown) return;

        _applyShutdown();
    }

    function _requireCallerIsPriceFeed() internal view virtual {
        if (msg.sender != address(priceFeed)) {
            revert CallerNotPriceFeed();
        }
    }

    function _applyShutdown() internal virtual {
        activePool.mintAggInterest();
        hasBeenShutDown = true;
        branchManager.shutdown();
    }

    // --- Helper functions ---

    function _reInsertIntoSortedTroves(
        uint256 _troveId,
        uint256 _troveAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        address _batchManager,
        uint256 _batchAnnualInterestRate
    ) internal virtual {
        // If it was in a batch, we need to put it back, otherwise we insert it normally
        if (_batchManager == address(0)) {
            sortedTroves.insert(_troveId, _troveAnnualInterestRate, _upperHint, _lowerHint);
        } else {
            sortedTroves.insertIntoBatch(
                _troveId, BatchId.wrap(_batchManager), _batchAnnualInterestRate, _upperHint, _lowerHint
            );
        }
    }

    // This function mints the BOLD corresponding to the borrower's chosen debt increase
    // (it does not mint the accrued interest).
    function _moveTokensFromAdjustment(
        address withdrawalReceiver,
        TroveChange memory _troveChange,
        IBoldToken _boldToken,
        IActivePool _activePool
    ) internal virtual {
        if (_troveChange.debtIncrease > 0) {
            _requireMintingDebtAllowed();
            _boldToken.mint(withdrawalReceiver, _troveChange.debtIncrease);
            _requireTotalDebtBelowCap();
        } else if (_troveChange.debtDecrease > 0) {
            _boldToken.burn(msg.sender, _troveChange.debtDecrease);
        }

        if (_troveChange.collIncrease > 0) {
            // Pull coll tokens from sender and move them to the Active Pool
            _pullCollAndSendToActivePool(_activePool, _troveChange.collIncrease);
        } else if (_troveChange.collDecrease > 0) {
            // Pull Coll from Active Pool and decrease its recorded Coll balance
            _activePool.sendColl(withdrawalReceiver, _troveChange.collDecrease);
        }
    }

    function _pullCollAndSendToActivePool(IActivePool _activePool, uint256 _amount) internal virtual {
        // Send Coll tokens from sender to active pool
        collToken.safeTransferFrom(msg.sender, address(_activePool), _amount);
        // Make sure Active Pool accountancy is right
        _activePool.accountForReceivedColl(_amount);
    }

    // --- 'Require' wrapper functions ---

    function _requireIsNotShutDown() internal view virtual {
        if (hasBeenShutDown) {
            revert IsShutDown();
        }
    }

    function _requireTroveDoesNotExists(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
        ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
        if (status != ITroveManager.Status.nonExistent) {
            revert TroveExists();
        }
    }

    function _requireIsInBatch(uint256 _troveId) internal view virtual returns (address) {
        address batchManager = interestBatchManagerOf[_troveId];
        if (batchManager == address(0)) {
            revert TroveNotInBatch();
        }

        return batchManager;
    }

    function _requireTroveIsOpen(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
        ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
        if (status != ITroveManager.Status.active && status != ITroveManager.Status.zombie) {
            revert TroveNotOpen();
        }
    }

    function _requireTroveIsActive(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
        ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
        if (status != ITroveManager.Status.active) {
            revert TroveNotActive();
        }
    }

    function _requireTotalDebtBelowCap() internal view virtual {
        if (activePool.totalDebtTaken() > branchManager.debtCap()) {
            if (msg.sender != _ebisuAdminRegistry.ebisuAdmin()) {
                revert DebtCapReached();
            }
        }
    }

    function _requireTroveIsZombie(ITroveManager _troveManager, uint256 _troveId) internal view virtual {
        if (!_checkTroveIsZombie(_troveManager, _troveId)) {
            revert TroveNotZombie();
        }
    }

    function _checkTroveIsZombie(ITroveManager _troveManager, uint256 _troveId) internal view virtual returns (bool) {
        ITroveManager.Status status = _troveManager.getTroveStatus(_troveId);
        return status == ITroveManager.Status.zombie;
    }

    function _requireNonZeroDebt(uint256 _troveDebt) internal pure virtual {
        if (_troveDebt == 0) {
            revert TroveWithZeroDebt();
        }
    }

    function _requireUserAcceptsUpfrontFee(uint256 _fee, uint256 _maxFee) internal pure virtual {
        if (_fee > _maxFee) {
            revert UpfrontFeeTooHigh();
        }
    }

    function _requireICRisAboveMCR(uint256 _newICR) internal view virtual {
        if (_newICR < branchManager.MCR()) {
            revert ICRBelowMCR();
        }
    }

    function _requireNewTCRisAboveCCR(uint256 _newTCR) internal view virtual {
        if (_newTCR < branchManager.CCR()) {
            revert TCRBelowCCR();
        }
    }

    function _requireOraclesLive() internal virtual returns (uint256) {
        (uint256 price, bool newOracleFailureDetected) = priceFeed.fetchPrice();
        if (newOracleFailureDetected) {
            revert NewOracleFailureDetected();
        }

        return price;
    }

    // --- ICR and TCR getters ---

    function _getNewTCRFromTroveChange(TroveChange memory _troveChange, uint256 _price)
        internal
        view
        virtual
        returns (uint256 newTCR)
    {
        uint256 totalColl = getEntireBranchColl();
        totalColl += _troveChange.collIncrease;
        totalColl -= _troveChange.collDecrease;

        uint256 totalDebt = getEntireBranchDebt();
        totalDebt += _troveChange.debtIncrease;
        totalDebt += _troveChange.upfrontFee;
        totalDebt -= _troveChange.debtDecrease;

        newTCR = LiquityMath._computeCR(totalColl, totalDebt, _price);
    }

    function setInterestBatchManagerOf(uint256 _troveId, address _batchManager) external virtual {
        if (msg.sender != address(boHelper)) {
            revert CallerNotBorrowerOperationsHelper();
        }
        interestBatchManagerOf[_troveId] = _batchManager;
    }

    function _requireICRisAboveMCRPlusBCR(uint256 _newICR) internal view virtual {
        if (_newICR < branchManager.MCR() + BCR) {
            revert ICRBelowMCRPlusBCR();
        }
    }

    function setMinDebt(uint256 _minDebt) external virtual {
        if (msg.sender != address(branchManager)) {
            revert CallerNotBranchManager();
        }
        minDebt = _minDebt;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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);

    /**
     * @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 `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, 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);

    /**
     * @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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
}
"
    },
    "lib/openzeppelin-con

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Non-Fungible, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xdb0cd67899e071a8798a58c9d2dd43795833c71d|verified:true|block:23676808|tx:0x8290fbddf0ec62d75ab74f03eaf0290334a07a9aa45e3074751e3f78fa983d4a|first_check:1761669055

Submitted on: 2025-10-28 17:30:57

Comments

Log in to comment.

No comments yet.