LiquityV2LBStrategy

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/Strategy.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {TroveOps} from "./libraries/TroveOps.sol";
import {LenderOps} from "./libraries/LenderOps.sol";
import {LiquityMath} from "./libraries/LiquityMath.sol";

import {IExchange} from "./interfaces/IExchange.sol";
import {AggregatorInterface} from "./interfaces/AggregatorInterface.sol";
import {
    IAddressesRegistry,
    IBorrowerOperations,
    ICollSurplusPool,
    ITroveManager
} from "./interfaces/IAddressesRegistry.sol";

import {BaseLenderBorrower, ERC20, Math} from "./BaseLenderBorrower.sol";

contract LiquityV2LBStrategy is BaseLenderBorrower {

    using SafeERC20 for ERC20;

    // ===============================================================
    // Storage
    // ===============================================================

    /// @notice Trove ID
    uint256 public troveId;

    /// @notice Absolute surplus required (in BOLD) before tending is considered
    uint256 public minSurplusAbsolute;

    /// @notice Relative surplus required (in basis points) of current debt, before tending is considered
    uint256 public minSurplusRelative;

    /// @notice Addresses allowed to deposit
    mapping(address => bool) public allowed;

    // ===============================================================
    // Constants
    // ===============================================================

    /// @notice The difference in decimals between our price oracle (1e8) and Liquity's price oracle (1e18)
    uint256 private constant _DECIMALS_DIFF = 1e10;

    /// @notice Maximum relative surplus required (in basis points) before tending is considered
    uint256 private constant _MAX_RELATIVE_SURPLUS = 1000; // 10%

    /// @notice WETH token
    ERC20 private constant _WETH = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    /// @notice Same Chainlink price feed as used by the Liquity branch
    AggregatorInterface public immutable PRICE_FEED;

    /// @notice Liquity's borrower operations contract
    IBorrowerOperations public immutable BORROWER_OPERATIONS;

    /// @notice Liquity's trove manager contract
    ITroveManager public immutable TROVE_MANAGER;

    /// @notice Liquity's collateral surplus pool contract
    ICollSurplusPool public immutable COLL_SURPLUS_POOL;

    /// @notice The exchange contract for buying/selling the borrow token
    IExchange public immutable EXCHANGE;

    // ===============================================================
    // Constructor
    // ===============================================================

    /// @param _addressesRegistry The Liquity addresses registry contract
    /// @param _priceFeed The price feed contract for the `asset`
    /// @param _exchange The exchange contract for buying/selling borrow token
    /// @param _name The name of the strategy
    constructor(
        IAddressesRegistry _addressesRegistry,
        AggregatorInterface _priceFeed,
        IExchange _exchange,
        string memory _name
    )
        BaseLenderBorrower(
            _addressesRegistry.collToken(), // asset
            _name,
            _addressesRegistry.boldToken(), // borrowToken
            address(0x89E93172AEF8261Db8437b90c3dCb61545a05317) // lenderVault (sUSDaf)
        )
    {
        require(_exchange.BORROW() == borrowToken && _exchange.COLLATERAL() == address(asset), "!exchange");

        minSurplusAbsolute = 50e18; // 50 BOLD
        minSurplusRelative = 100; // 1%

        BORROWER_OPERATIONS = _addressesRegistry.borrowerOperations();
        TROVE_MANAGER = _addressesRegistry.troveManager();
        COLL_SURPLUS_POOL = _addressesRegistry.collSurplusPool();
        PRICE_FEED =
            address(_priceFeed) == address(0) ? _addressesRegistry.priceFeed().ethUsdOracle().aggregator : _priceFeed;
        require(PRICE_FEED.decimals() == 8, "!priceFeed");
        EXCHANGE = _exchange;

        ERC20(borrowToken).forceApprove(address(EXCHANGE), type(uint256).max);
        asset.forceApprove(address(EXCHANGE), type(uint256).max);
        asset.forceApprove(address(BORROWER_OPERATIONS), type(uint256).max);
        if (asset != _WETH) _WETH.forceApprove(address(BORROWER_OPERATIONS), TroveOps.ETH_GAS_COMPENSATION);
    }

    // ===============================================================
    // Privileged functions
    // ===============================================================

    /// @notice Open a trove
    /// @dev Callable only once. If the position gets liquidated, we'll need to shutdown the strategy
    /// @dev `asset` balance must be large enough to open a trove with `MIN_DEBT`
    /// @dev Requires the caller to pay the gas compensation in WETH
    /// @dev Should be called through a private RPC to avoid fee slippage
    /// @param _upperHint Upper hint
    /// @param _lowerHint Lower hint
    function openTrove(uint256 _upperHint, uint256 _lowerHint) external onlyEmergencyAuthorized {
        require(troveId == 0, "troveId");

        // Mint `MIN_DEBT` and use all the collateral we have
        troveId = TroveOps.openTrove(BORROWER_OPERATIONS, balanceOfAsset(), _upperHint, _lowerHint);

        // Lend everything we have
        _lendBorrowToken(balanceOfBorrowToken());
    }

    /// @notice Adjust the interest rate of the trove
    /// @dev Will fail if the trove is not active
    /// @dev Should be called through a private RPC to avoid fee slippage
    /// @dev Would incur an upfront fee if the adjustment is considered premature (i.e. within 7 days of last adjustment)
    /// @param _newAnnualInterestRate New annual interest rate
    /// @param _upperHint Upper hint
    /// @param _lowerHint Lower hint
    function adjustTroveInterestRate(
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint
    ) external onlyEmergencyAuthorized {
        TroveOps.adjustTroveInterestRate(BORROWER_OPERATIONS, troveId, _newAnnualInterestRate, _upperHint, _lowerHint);
    }

    /// @notice Manually buy borrow token
    /// @dev Potentially can never reach `_buyBorrowToken()` in `_liquidatePosition()`
    ///      because of lender vault accounting (i.e. `balanceOfLentAssets() == 0` is never true)
    function buyBorrowToken(
        uint256 _amount
    ) external onlyEmergencyAuthorized {
        if (_amount == type(uint256).max) _amount = balanceOfAsset();
        _buyBorrowToken(_amount);
    }

    /// @notice Adjust zombie trove
    /// @dev Might need to be called after a redemption if our debt is below `MIN_DEBT`
    /// @dev Will fail if the trove is not in zombie mode
    /// @dev Should be called through a private RPC to avoid fee slippage
    /// @param _upperHint Upper hint
    /// @param _lowerHint Lower hint
    function adjustZombieTrove(uint256 _upperHint, uint256 _lowerHint) external onlyEmergencyAuthorized {
        // Mint just enough to get the trove out of zombie mode, using all the collateral we have
        TroveOps.adjustZombieTrove(
            BORROWER_OPERATIONS, troveId, balanceOfAsset(), balanceOfDebt(), _upperHint, _lowerHint
        );

        // Lend everything we have
        _lendBorrowToken(balanceOfBorrowToken());
    }

    /// @notice Set the surplus detection floors used by `hasBorrowTokenSurplus()`
    /// @param _minSurplusAbsolute Absolute minimum surplus required, in borrow token units
    /// @param _minSurplusRelative Relative minimum surplus required, as basis points of current debt
    function setSurplusFloors(uint256 _minSurplusAbsolute, uint256 _minSurplusRelative) external onlyManagement {
        require(_minSurplusRelative <= _MAX_RELATIVE_SURPLUS, "!relativeSurplus");
        minSurplusAbsolute = _minSurplusAbsolute;
        minSurplusRelative = _minSurplusRelative;
    }

    /// @notice Allow (or disallow) a specific address to deposit
    /// @dev Deposits can trigger new borrows, and Liquity charges an upfront fee on
    ///      every debt increase. If deposits were permissionless, a malicious actor
    ///      could repeatedly deposit/withdraw small amounts to force the strategy
    ///      to borrow and pay the fee many times, socializing those costs across
    ///      existing depositors. To prevent this griefing vector, deposits are gated
    /// @param _address Address to allow or disallow
    /// @param _allowed True to allow deposits from `_address`, false to block
    function setAllowed(address _address, bool _allowed) external onlyManagement {
        allowed[_address] = _allowed;
    }

    /// @notice Sweep of non-asset ERC20 tokens
    /// @param _token The ERC20 token to sweep
    function sweep(
        ERC20 _token
    ) external onlyManagement {
        require(_token != asset, "!asset");
        _token.safeTransfer(TokenizedStrategy.management(), _token.balanceOf(address(this)));
    }

    // ===============================================================
    // Write functions
    // ===============================================================

    /// @inheritdoc BaseLenderBorrower
    function _leveragePosition(
        uint256 _amount
    ) internal override {
        // Do nothing if the trove is not active
        if (TROVE_MANAGER.getTroveStatus(troveId) != ITroveManager.Status.active) return;

        // Otherwise, business as usual
        BaseLenderBorrower._leveragePosition(_amount);
    }

    /// @inheritdoc BaseLenderBorrower
    function _supplyCollateral(
        uint256 _amount
    ) internal override {
        if (_amount > 0) BORROWER_OPERATIONS.addColl(troveId, _amount);
    }

    /// @inheritdoc BaseLenderBorrower
    function _withdrawCollateral(
        uint256 _amount
    ) internal override {
        if (_amount > 0) BORROWER_OPERATIONS.withdrawColl(troveId, _amount);
    }

    /// @inheritdoc BaseLenderBorrower
    function _borrow(
        uint256 _amount
    ) internal override {
        if (_amount > 0) BORROWER_OPERATIONS.withdrawBold(troveId, _amount, type(uint256).max);
    }

    /// @inheritdoc BaseLenderBorrower
    function _repay(
        uint256 _amount
    ) internal override {
        // `repayBold()` makes sure we don't go below `MIN_DEBT`
        if (_amount > 0) BORROWER_OPERATIONS.repayBold(troveId, _amount);
    }

    // ===============================================================
    // View functions
    // ===============================================================

    /// @inheritdoc BaseLenderBorrower
    function availableDepositLimit(
        address _owner
    ) public view override returns (uint256) {
        // We check `_owner == address(this)` because BaseLenderBorrower uses `availableDepositLimit(address(this))`
        return allowed[_owner] || _owner == address(this) ? BaseLenderBorrower.availableDepositLimit(_owner) : 0;
    }

    /// @inheritdoc BaseLenderBorrower
    /// @dev Returns the maximum collateral that can be withdrawn without reducing
    ///      the branch’s total collateral ratio (TCR) below the critical collateral ratio (CCR).
    ///      Withdrawal is limited by both:
    ///        - the CCR constraint at the branch level, and
    ///        - the base withdrawal constraints in BaseLenderBorrower
    function _maxWithdrawal() internal view override returns (uint256) {
        // Cache values for later use
        uint256 _price = _getPrice(address(asset)) * _DECIMALS_DIFF;
        uint256 _branchDebt = BORROWER_OPERATIONS.getEntireBranchDebt();
        uint256 _branchCollateral = BORROWER_OPERATIONS.getEntireBranchColl();

        // Collateral required to keep TCR >= CCR
        uint256 _requiredColl = Math.ceilDiv(BORROWER_OPERATIONS.CCR() * _branchDebt, _price);

        // Max collateral removable while staying >= CCR
        uint256 _headroomByCCR = _branchCollateral > _requiredColl ? _branchCollateral - _requiredColl : 0;

        // Final cap is the tighter of CCR constraint and base withdrawal cap
        return Math.min(_headroomByCCR, BaseLenderBorrower._maxWithdrawal());
    }

    /// @inheritdoc BaseLenderBorrower
    function _getPrice(
        address _asset
    ) internal view override returns (uint256) {
        // Not bothering with price feed checks because it's the same one Liquity uses
        (, int256 _answer,,,) = PRICE_FEED.latestRoundData();
        return _asset == borrowToken ? WAD / _DECIMALS_DIFF : uint256(_answer);
    }

    /// @inheritdoc BaseLenderBorrower
    function _isSupplyPaused() internal pure override returns (bool) {
        return false;
    }

    /// @inheritdoc BaseLenderBorrower
    function _isBorrowPaused() internal view override returns (bool) {
        // When the branch TCR falls below the CCR, BorrowerOperations blocks `withdrawBold()`,
        // in that case we should not attempt to borrow.
        // When TCR >= CCR, our own target (<= 90% of MCR) is stricter than the CCR requirement,
        // so don’t need to impose an additional cap in `_maxBorrowAmount()`
        return _isTCRBelowCCR();
    }

    /// @inheritdoc BaseLenderBorrower
    function _isLiquidatable() internal view override returns (bool) {
        // `getCurrentICR()` expects the price to be in 1e18 format
        return
            TROVE_MANAGER.getCurrentICR(troveId, _getPrice(address(asset)) * _DECIMALS_DIFF) < BORROWER_OPERATIONS.MCR();
    }

    /// @inheritdoc BaseLenderBorrower
    function _maxCollateralDeposit() internal pure override returns (uint256) {
        return type(uint256).max;
    }

    /// @inheritdoc BaseLenderBorrower
    function _maxBorrowAmount() internal pure override returns (uint256) {
        return type(uint256).max;
    }

    /// @inheritdoc BaseLenderBorrower
    function getNetBorrowApr(
        uint256 /*_newAmount*/
    ) public pure override returns (uint256) {
        return 0; // Assumes always profitable to borrow
    }

    /// @inheritdoc BaseLenderBorrower
    function getNetRewardApr(
        uint256 /*_newAmount*/
    ) public pure override returns (uint256) {
        return 1; // Assumes always profitable to borrow
    }

    /// @inheritdoc BaseLenderBorrower
    function getLiquidateCollateralFactor() public view override returns (uint256) {
        return WAD * WAD / BORROWER_OPERATIONS.MCR();
    }

    /// @inheritdoc BaseLenderBorrower
    function balanceOfCollateral() public view override returns (uint256) {
        return TROVE_MANAGER.getLatestTroveData(troveId).entireColl;
    }

    /// @inheritdoc BaseLenderBorrower
    function balanceOfDebt() public view override returns (uint256) {
        return TROVE_MANAGER.getLatestTroveData(troveId).entireDebt;
    }

    /// @notice Returns true when we hold more borrow token than we owe by a _meaningful_ margin
    /// @dev Purpose is to detect redemptions/liquidations while avoiding action on normal profit
    /// @return True if `surplus > max(absoluteFloor, relativeFloor)`
    function hasBorrowTokenSurplus() public view returns (bool) {
        uint256 _loose = balanceOfBorrowToken();
        uint256 _have = balanceOfLentAssets() + _loose;
        uint256 _owe = balanceOfDebt();
        if (_have <= _owe) return false;

        // Positive surplus we could realize by selling borrow token back to collateral
        uint256 _surplus = _have - _owe;

        // Use the stricter of the two floors (absolute or relative)
        uint256 _floor = Math.max(
            minSurplusAbsolute, // Absolute floor
            _owe * minSurplusRelative / MAX_BPS // Relative floor (some percentage of current debt)
        );

        // Consider surplus only when higher than the higher floor
        return _surplus > _floor;
    }

    /// @notice Check if the branch Total Collateral Ratio (TCR) has fallen below the Critical Collateral Ratio (CCR)
    /// @return True if TCR < CCR, false otherwise
    function _isTCRBelowCCR() internal view returns (bool) {
        return LiquityMath._computeCR(
            BORROWER_OPERATIONS.getEntireBranchColl(),
            BORROWER_OPERATIONS.getEntireBranchDebt(),
            _getPrice(address(asset)) * _DECIMALS_DIFF // LiquityMath expects 1e18 format
        ) < BORROWER_OPERATIONS.CCR();
    }

    // ===============================================================
    // Lender vault
    // ===============================================================

    /// @inheritdoc BaseLenderBorrower
    function _lendBorrowToken(
        uint256 _amount
    ) internal override {
        LenderOps.lend(lenderVault, _amount);
    }

    /// @inheritdoc BaseLenderBorrower
    function _withdrawBorrowToken(
        uint256 _amount
    ) internal override {
        LenderOps.withdraw(lenderVault, _amount);
    }

    /// @inheritdoc BaseLenderBorrower
    function _lenderMaxDeposit() internal view override returns (uint256) {
        return LenderOps.maxDeposit(lenderVault);
    }

    /// @inheritdoc BaseLenderBorrower
    function _lenderMaxWithdraw() internal view override returns (uint256) {
        return LenderOps.maxWithdraw(lenderVault);
    }

    /// @inheritdoc BaseLenderBorrower
    function balanceOfLentAssets() public view override returns (uint256) {
        return LenderOps.balanceOfAssets(lenderVault);
    }

    // ===============================================================
    // Harvest / Token conversions
    // ===============================================================

    /// @inheritdoc BaseLenderBorrower
    function _tendTrigger() internal view override returns (bool) {
        // If base fee is acceptable and we have a borrow token surplus (likely from redemption/liquidation),
        // tend to (1) minimize exchange rate exposure and (2) minimize the risk of someone using our borrowing capacity
        // before we manage to borrow again, such that any new debt we take will lead to TCR < CCR
        //
        // (2) chain of events: [1] we are redeemed [2] we have no debt but some collateral
        // [3] someone hops in and uses our collateral to borrow above CCR [4] we cannot take new debt because it will lead to TCR < CCR
        if (_isBaseFeeAcceptable() && hasBorrowTokenSurplus()) return true;

        // If the trove is not active, do nothing
        if (TROVE_MANAGER.getTroveStatus(troveId) != ITroveManager.Status.active) return false;

        // Finally, business as usual
        return BaseLenderBorrower._tendTrigger();
    }

    /// @inheritdoc BaseLenderBorrower
    function _tend(
        uint256 /*_totalIdle*/
    ) internal override {
        // Sell any surplus borrow token
        _claimAndSellRewards();

        // Using `balanceOfAsset()` because `_totalIdle` may increase after selling borrow token surplus
        return BaseLenderBorrower._tend(balanceOfAsset());
    }

    /// @inheritdoc BaseLenderBorrower
    function _claimRewards() internal pure override {
        return;
    }

    /// @inheritdoc BaseLenderBorrower
    function _claimAndSellRewards() internal override {
        uint256 _loose = balanceOfBorrowToken();
        uint256 _have = balanceOfLentAssets() + _loose;
        uint256 _owe = balanceOfDebt();
        if (_owe >= _have) return;

        uint256 _toSell = _have - _owe;
        if (_toSell > _loose) _withdrawBorrowToken(_toSell - _loose);

        _loose = balanceOfBorrowToken();

        _sellBorrowToken(Math.min(_toSell, _loose));
    }

    /// @inheritdoc BaseLenderBorrower
    function _sellBorrowToken(
        uint256 _amount
    ) internal override {
        EXCHANGE.swap(
            _amount,
            0, // minAmount
            true // fromBorrow
        );
    }

    /// @inheritdoc BaseLenderBorrower
    function _buyBorrowToken() internal virtual override {
        uint256 _borrowTokenStillOwed = borrowTokenOwedBalance();
        uint256 _maxAssetBalance = _fromUsd(_toUsd(_borrowTokenStillOwed, borrowToken), address(asset));
        _buyBorrowToken(_maxAssetBalance);
    }

    /// @notice Buy borrow token
    /// @param _amount The amount of asset to sale
    function _buyBorrowToken(
        uint256 _amount
    ) internal {
        EXCHANGE.swap(
            _amount,
            0, // minAmount
            false // fromBorrow
        );
    }

    /// @inheritdoc BaseLenderBorrower
    function _emergencyWithdraw(
        uint256 _amount
    ) internal override {
        if (_amount > 0) _withdrawBorrowToken(Math.min(_amount, _lenderMaxWithdraw()));
        TroveOps.onEmergencyWithdraw(
            TROVE_MANAGER, BORROWER_OPERATIONS, COLL_SURPLUS_POOL, TokenizedStrategy.management(), troveId
        );
    }

}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {

    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }

}
"
    },
    "src/libraries/TroveOps.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;

import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {ITroveManager} from "../interfaces/ITroveManager.sol";
import {ICollSurplusPool} from "../interfaces/ICollSurplusPool.sol";
import {IBorrowerOperations} from "../interfaces/IBorrowerOperations.sol";

library TroveOps {

    using SafeERC20 for IERC20;

    // ===============================================================
    // Constants
    // ===============================================================

    /// @notice Liquity's minimum amount of net Bold debt a trove must have
    ///         If a trove is redeeemed and the debt is less than this, it will be considered a zombie trove
    uint256 public constant MIN_DEBT = 2_000 * 1e18;

    /// @notice Liquity's amount of WETH to be locked in gas pool when opening a trove
    ///         Will be pulled from the contract on `_openTrove`
    uint256 public constant ETH_GAS_COMPENSATION = 0.0375 ether;

    /// @notice Minimum annual interest rate
    uint256 public constant MIN_ANNUAL_INTEREST_RATE = 1e18 / 100 / 2; // 0.5%

    /// @notice WETH token
    IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    // ===============================================================
    // Write functions
    // ===============================================================

    /// @notice Opens a trove with the given parameters
    /// @dev Requires the caller to pay the gas compensation in WETH
    /// @dev Should be called through a private RPC to avoid fee slippage
    /// @param _borrowerOperations The borrower operations contract
    /// @param _collAmount The amount of collateral to deposit
    /// @param _upperHint The upper hint for the trove
    /// @param _lowerHint The lower hint for the trove
    /// @return The ID of the newly opened trove
    function openTrove(
        IBorrowerOperations _borrowerOperations,
        uint256 _collAmount,
        uint256 _upperHint,
        uint256 _lowerHint
    ) external returns (uint256) {
        WETH.safeTransferFrom(msg.sender, address(this), ETH_GAS_COMPENSATION);
        return _borrowerOperations.openTrove(
            address(this), // owner
            block.timestamp, // ownerIndex
            _collAmount,
            MIN_DEBT, // boldAmount
            _upperHint,
            _lowerHint,
            MIN_ANNUAL_INTEREST_RATE, // annualInterestRate
            type(uint256).max, // maxUpfrontFee
            address(0), // addManager
            address(0), // removeManager
            address(0) // receiver
        );
    }

    /// @notice Adjust the interest rate of the trove
    /// @dev Will fail if the trove is not active
    /// @dev Should be called through a private RPC to avoid fee slippage
    /// @param _borrowerOperations The borrower operations contract
    /// @param _troveId The ID of the trove to adjust
    /// @param _newAnnualInterestRate New annual interest rate
    /// @param _upperHint Upper hint
    /// @param _lowerHint Lower hint
    function adjustTroveInterestRate(
        IBorrowerOperations _borrowerOperations,
        uint256 _troveId,
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint
    ) external {
        _borrowerOperations.adjustTroveInterestRate(
            _troveId,
            _newAnnualInterestRate,
            _upperHint,
            _lowerHint,
            type(uint256).max // maxUpfrontFee
        );
    }

    /// @notice Adjust zombie trove
    /// @dev Might need to be called after a redemption, if our debt is below `MIN_DEBT`
    /// @dev Will fail if the trove is not in zombie mode
    /// @dev Should be called through a private RPC to avoid fee slippage
    /// @param _borrowerOperations The borrower operations contract
    /// @param _troveId The ID of the trove to adjust
    /// @param _balanceOfAsset Balance of asset
    /// @param _balanceOfDebt Balance of debt
    /// @param _upperHint Upper hint
    /// @param _lowerHint Lower hint
    function adjustZombieTrove(
        IBorrowerOperations _borrowerOperations,
        uint256 _troveId,
        uint256 _balanceOfAsset,
        uint256 _balanceOfDebt,
        uint256 _upperHint,
        uint256 _lowerHint
    ) external {
        _borrowerOperations.adjustZombieTrove(
            _troveId,
            _balanceOfAsset, // collChange
            true, // isCollIncrease
            MIN_DEBT - _balanceOfDebt, // boldChange
            true, // isDebtIncrease
            _upperHint,
            _lowerHint,
            type(uint256).max // maxUpfrontFee
        );
    }

    /// @notice Close the trove if it's active or try to claim leftover collateral if we were liquidated
    /// @dev `_management` will get back the ETH gas compensation if we're closing the trove
    /// @param _troveManager The trove manager contract
    /// @param _borrowerOperations The borrower operations contract
    /// @param _borrowerOperations The collateral surplus pool contract
    /// @param _management The management address
    /// @param _troveId The ID of the trove
    function onEmergencyWithdraw(
        ITroveManager _troveManager,
        IBorrowerOperations _borrowerOperations,
        ICollSurplusPool _collSurplusPool,
        address _management,
        uint256 _troveId
    ) external {
        if (_troveManager.getTroveStatus(_troveId) == ITroveManager.Status.active) {
            _borrowerOperations.closeTrove(_troveId);
            if (WETH.balanceOf(address(this)) >= ETH_GAS_COMPENSATION) {
                WETH.safeTransfer(_management, ETH_GAS_COMPENSATION);
            }
        } else if (_troveManager.getTroveStatus(_troveId) == ITroveManager.Status.closedByLiquidation) {
            if (_collSurplusPool.getCollateral(address(this)) > 0) _borrowerOperations.claimCollateral();
        }
    }

}
"
    },
    "src/libraries/LenderOps.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";

library LenderOps {

    // ===============================================================
    // View functions
    // ===============================================================

    /// @notice Returns the maximum amount of borrowed token we can lend
    /// @param _vault The vault contract (i.e. sUSDaf)
    /// @return The maximum amount of borrowed token we can lend
    function maxDeposit(IERC4626 _vault) external view returns (uint256) {
        return _vault.maxDeposit(address(this));
    }

    /// @notice Returns the maximum amount of assets we can withdraw from the vault
    /// @param _vault The vault contract (i.e. sUSDaf)
    /// @return The maximum amount of borrowed token we can withdraw from the vault
    function maxWithdraw(IERC4626 _vault) external view returns (uint256) {
        return _vault.maxWithdraw(address(this));
    }

    /// @notice Returns the amount of borrow token we have lent
    /// @param _vault The vault contract (i.e. sUSDaf)
    /// @return The amount of borrow token we have lent
    function balanceOfAssets(IERC4626 _vault) external view returns (uint256) {
        return _vault.convertToAssets(_vault.balanceOf(address(this)));
    }

    // ===============================================================
    // Write functions
    // ===============================================================

    /// @notice Deposits borrowed tokens into the vault
    /// @param _vault The vault contract (i.e. sUSDaf)
    /// @param _amount The amount of tokens to deposit
    function lend(IERC4626 _vault, uint256 _amount) external {
        _vault.deposit(_amount, address(this));
    }

    /// @notice Withdraws tokens from the vault
    /// @param _vault The vault contract (i.e. sUSDaf)
    /// @param _amount The amount of tokens to withdraw
    function withdraw(IERC4626 _vault, uint256 _amount) external {
        if (_amount > 0) {
            // How much sUSDaf
            uint256 _shares = Math.min(_vault.previewWithdraw(_amount), _vault.balanceOf(address(this)));

            // Redeem sUSDaf to USDaf
            _vault.redeem(_shares, address(this), address(this));
        }
    }

}
"
    },
    "src/libraries/LiquityMath.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

library LiquityMath {

    uint256 constant DECIMAL_PRECISION = 1e18;

    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;
    }

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

    /* 
    * 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 function CollateralRegistry._calcDecayedBaseRate, that represent time in units of minutes
    *
    * 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 _computeCR(uint256 _coll, uint256 _debt, uint256 _price) internal pure returns (uint256) {
        if (_debt > 0) {
            uint256 newCollRatio = _coll * _price / _debt;

            return newCollRatio;
        }
        // Return the maximal value for uint256 if the debt is 0. Represents "infinite" CR.
        else {
            // if (_debt == 0)
            return 2 ** 256 - 1;
        }
    }

}
"
    },
    "src/interfaces/IExchange.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IExchange {

    function SMS() external view returns (address);
    function BORROW() external view returns (address);
    function COLLATERAL() external view returns (address);
    function swap(uint256 _amount, uint256 _minAmount, bool _fromBorrow) external returns (uint256);
    function sweep(
        IERC20 _token
    ) external;

}
"
    },
    "src/interfaces/AggregatorInterface.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;

interface AggregatorInterface {

    function decimals() external view returns (uint8);
    function latestAnswer() external view returns (int256);
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

}
"
    },
    "src/interfaces/IAddressesRegistry.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;

import {IPriceFeed} from "./IPriceFeed.sol";
import {ICollSurplusPool} from "./ICollSurplusPool.sol";
import {IBorrowerOperations} from "./IBorrowerOperations.sol";
import {ITroveManager} from "./ITroveManager.sol";

interface IAddressesRegistry {

    function collToken() external view returns (address);
    function boldToken() external view returns (address);
    function priceFeed() external view returns (IPriceFeed);
    function collSurplusPool() external view returns (ICollSurplusPool);
    function borrowerOperations() external view returns (IBorrowerOperations);
    function troveManager() external view returns (ITroveManager);

}
"
    },
    "src/BaseLenderBorrower.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {BaseHealthCheck, ERC20} from "@periphery/Bases/HealthCheck/BaseHealthCheck.sol";

/**
 * @title Base Lender Borrower
 */
abstract contract BaseLenderBorrower is BaseHealthCheck {

    using SafeERC20 for ERC20;

    uint256 internal constant WAD = 1e18;

    /// The token we will be borrowing/supplying.
    address public immutable borrowToken;

    /// If set to true, the strategy will not try to repay debt by selling rewards or asset.
    bool public leaveDebtBehind;

    /// @notice Target Loan-To-Value (LTV) multiplier in Basis Points
    /// @dev Represents the ratio up to which we will borrow, relative to the liquidation threshold.
    /// LTV is the debt-to-collateral ratio. Default is set to 70% of the liquidation LTV.
    uint16 public targetLTVMultiplier;

    /// @notice Warning Loan-To-Value (LTV) multiplier in Basis Points
    /// @dev Represents the ratio at which we will start repaying the debt to avoid liquidation
    /// Default is set to 80% of the liquidation LTV
    uint16 public warningLTVMultiplier; // 80% of liquidation LTV

    /// @notice Slippage tolerance (in basis points) for swaps
    uint64 public slippage;

    /// @notice Deposit limit for the strategy.
    uint256 public depositLimit;

    /// The max the base fee (in gwei) will be for a tend
    uint256 public maxGasPriceToTend;

    /// Thresholds: lower limit on how much base token can be borrowed at a time.
    uint256 internal minAmountToBorrow;

    /// The lender vault that will be used to lend and borrow.
    IERC4626 public immutable lenderVault;

    /**
     * @param _asset The address of the asset we are lending/borrowing.
     * @param _name The name of the strategy.
     * @param _borrowToken The address of the borrow token.
     */
    constructor(
        address _asset,
        string memory _name,
        address _borrowToken,
        address _lenderVault
    ) BaseHealthCheck(_asset, _name) {
        borrowToken = _borrowToken;

        // Set default variables
        depositLimit = type(uint256).max;
        targetLTVMultiplier = 7_000;
        warningLTVMultiplier = 8_000;
        leaveDebtBehind = false;
        maxGasPriceToTend = 200 * 1e9;
        slippage = 500;

        // Allow for address(0) for versions that don't use 4626 vault.
        if (_lenderVault != address(0)) {
            lenderVault = IERC4626(_lenderVault);
            require(lenderVault.asset() == _borrowToken, "!lenderVault");
            ERC20(_borrowToken).safeApprove(_lenderVault, type(uint256).max);
        }
    }

    /// ----------------- SETTERS -----------------

    /**
     * @notice Set the deposit limit for the strategy
     * @param _depositLimit New deposit limit
     */
    function setDepositLimit(
        uint256 _depositLimit
    ) external onlyManagement {
        depositLimit = _depositLimit;
    }

    /**
     * @notice Set the target and warning LTV multipliers
     * @param _targetLTVMultiplier New target LTV multiplier
     * @param _warningLTVMultiplier New warning LTV multiplier
     * @dev Target must be less than warning, warning must be <= 9000, target cannot be 0
     */
    function setLtvMultipliers(uint16 _targetLTVMultiplier, uint16 _warningLTVMultiplier) external onlyManagement {
        require(
            _warningLTVMultiplier <= 9_000 && _targetLTVMultiplier < _warningLTVMultiplier && _targetLTVMultiplier != 0,
            "invalid LTV"
        );
        targetLTVMultiplier = _targetLTVMultiplier;
        warningLTVMultiplier = _warningLTVMultiplier;
    }

    /**
     * @notice Set whether to leave debt behind
     * @param _leaveDebtBehind New leave debt behind setting
     */
    function setLeaveDebtBehind(
        bool _leaveDebtBehind
    ) external onlyManagement {
        leaveDebtBehind = _leaveDebtBehind;
    }

    /**
     * @notice Set the maximum gas price for tending
     * @param _maxGasPriceToTend New maximum gas price
     */
    function setMaxGasPriceToTend(
        uint256 _maxGasPriceToTend
    ) external onlyManagement {
        maxGasPriceToTend = _maxGasPriceToTend;
    }

    /**
     * @notice Set the slippage tolerance
     * @param _slippage New slippage tolerance in basis points
     */
    function setSlippage(
        uint256 _slippage
    ) external onlyManagement {
        require(_slippage < MAX_BPS, "slippage");
        slippage = uint64(_slippage);
    }

    /*//////////////////////////////////////////////////////////////
                NEEDED TO BE OVERRIDDEN BY STRATEGIST
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Should deploy up to '_amount' of 'asset' in the yield source.
     *
     * This function is called at the end of a {deposit} or {mint}
     * call. Meaning that unless a whitelist is implemented it will
     * be entirely permissionless and thus can be sandwiched or otherwise
     * manipulated.
     *
     * @param _amount The amount of 'asset' that the strategy should attempt
     * to deposit in the yield source.
     */
    function _deployFunds(
        uint256 _amount
    ) internal virtual override {
        _leveragePosition(_amount);
    }

    /**
     * @dev Will attempt to free the '_amount' of 'asset'.
     *
     * The amount of 'asset' that is already loose has already
     * been accounted for.
     *
     * This function is called during {withdraw} and {redeem} calls.
     * Meaning that unless a whitelist is implemented it will be
     * entirely permissionless and thus can be sandwiched or otherwise
     * manipulated.
     *
     * Should not rely on asset.balanceOf(address(this)) calls other than
     * for diff accounting purposes.
     *
     * Any difference between `_amount` and what is actually freed will be
     * counted as a loss and passed on to the withdrawer. This means
     * care should be taken in times of illiquidity. It may be better to revert
     * if withdraws are simply illiquid so not to realize incorrect losses.
     *
     * @param _amount, The amount of 'asset' to be freed.
     */
    function _freeFunds(
        uint256 _amount
    ) internal virtual override {
        _liquidatePosition(_amount);
    }

    /**
     * @dev Internal function to harvest all rewards, redeploy any idle
     * funds and return an accurate accounting of all funds currently
     * held by the Strategy.
     *
     * This should do any needed harvesting, rewards selling, accrual,
     * redepositing etc. to get the most accurate view of current assets.
     *
     * NOTE: All applicable assets including loose assets should be
     * accounted for in this function.
     *
     * Care should be taken when relying on oracles or swap values rather
     * than actual amounts as all Strategy profit/loss accounting will
     * be done based on this returned value.
     *
     * This can still be called post a shutdown, a strategist can check
     * `TokenizedStrategy.isShutdown()` to decide if funds should be
     * redeployed or simply realize any profits/losses.
     *
     * @return _totalAssets A trusted and accurate account for the total
     * amount of 'asset' the strategy currently holds including idle funds.
     */
    function _harvestAndReport() internal virtual override returns (uint256 _totalAssets) {
        /// 1. claim rewards, 2. even borrowToken deposits and borrows 3. sell remainder of rewards to asset.
        _claimAndSellRewards();

        /// Leverage all the asset we have or up to the supply cap.
        /// We want check our leverage even if balance of asset is 0.
        _leveragePosition(Math.min(balanceOfAsset(), availableDepositLimit(address(this))));

        /// Base token owed should be 0 here but we count it just in case
        _totalAssets = balanceOfAsset() + balanceOfCollateral() - _borrowTokenOwedInAsset();
    }

    /*//////////////////////////////////////////////////////////////
                    OPTIONAL TO OVERRIDE BY STRATEGIST
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Optional function for strategist to override that can
     *  be called in between reports.
     *
     * If '_tend' is used tendTrigger() will also need to be overridden.
     *
     * This call can only be called by a permissioned role so may be
     * through protected relays.
     *
     * This can be used to harvest and compound rewards, deposit idle funds,
     * perform needed position maintenance or anything else that doesn't need
     * a full report for.
     *
     *   EX: A strategy that can not deposit funds without getting
     *       sandwiched can use the tend when a certain threshold
     *       of idle to totalAssets has been reached.
     *
     * The TokenizedStrategy contract will do all needed debt and idle updates
     * after this has finished and will have no effect on PPS of the strategy
     * till report() is called.
     *
     * @param _totalIdle The current amount of idle funds that are available to deploy.
     */
    function _tend(
        uint256 _totalIdle
    ) internal virtual override {
        /// If the cost to borrow > rewards rate we will pull out all funds to not report a loss
        if (getNetBorrowApr(0) > getNetRewardApr(0)) {
            /// Liquidate everything so not to report a loss
            _liquidatePosition(balanceOfCollateral());
            /// Return since we don't asset to do anything else
            return;
        }

        /// Else we need to either adjust LTV up or down.
        _leveragePosition(Math.min(_totalIdle, availableDepositLimit(address(this))));
    }

    /**
     * @dev Optional trigger to override if tend() will be used by the strategy.
     * This must be implemented if the strategy hopes to invoke _tend().
     *
     * @return . Should return true if tend() should be called by keeper or false if not.
     */
    function _tendTrigger() internal view virtual override returns (bool) {
        /// If we are in danger of being liquidated tend no matter what
        if (_isLiquidatable()) return true;

        if (TokenizedStrategy.totalAssets() == 0) return false;

        /// We adjust position if:
        /// 1. LTV ratios are not in the HEALTHY range (either we take on more debt or repay debt)
        /// 2. costs are acceptable
        uint256 collateralInUsd = _toUsd(balanceOfCollateral(), address(asset));
        uint256 debtInUsd = _toUsd(balanceOfDebt(), borrowToken);
        uint256 currentLTV = collateralInUsd > 0 ? (debtInUsd * WAD) / collateralInUsd : 0;

        /// Check if we are over our warning LTV
        if (currentLTV > _getWarningLTV()) return true;

        if (_isSupplyPaused() || _isBorrowPaused()) return false;

        uint256 targetLTV = _getTargetLTV();

        /// If we are still levered and Borrowing costs are too high.
        if (currentLTV != 0 && getNetBorrowApr(0) > getNetRewardApr(0)) {
            /// Tend if base fee is acceptable.
            return _isBaseFeeAcceptable();

            /// IF we are lower than our target. (we need a 10% (1000bps) difference)
        } else if ((currentLTV < targetLTV && targetLTV - currentLTV > 1e17)) {
            /// Make sure the increase in debt would keep borrowing costs healthy.
            uint256 targetDebtUsd = (collateralInUsd * targetLTV) / WAD;

            uint256 amountToBorrowUsd;
            unchecked {
                amountToBorrowUsd = targetDebtUsd - debtInUsd; // safe bc we checked ratios
            }

            /// Convert to borrowToken
            uint256 amountToBorrowBT =
                Math.min(_fromUsd(amountToBorrowUsd, borrowToken), Math.min(_lenderMaxDeposit(), _maxBorrowAmount()));

            if (amountToBorrowBT == 0) return false;

            /// We want to make sure that the reward apr > borrow apr so we don't report a loss
            /// Borrowing will cause the borrow apr to go up and the rewards apr to go down
            if (getNetBorrowApr(amountToBorrowBT) < getNetRewardApr(amountToBorrowBT)) {
                /// Borrowing costs are healthy and WE NEED TO TAKE ON MORE DEBT
                return _isBaseFeeAcceptable();
            }
        }

        return false;
    }

    /**
     * @notice Gets the max amount of `asset` that an address can deposit.
     * @dev Defaults to an unlimited amount for any address. But can
     * be overridden by strategists.
     *
     * This function will be called before any deposit or mints to enforce
     * any limits desired by the strategist. This can be used for either a
     * traditional deposit limit or for implementing a whitelist etc.
     *
     *   EX:
     *      if(isAllowed[_owner]) return super.availableDepositLimit(_owner);
     *
     * This does not need to take into account any conversion rates
     * from shares to assets. But should know that any non max uint256
     * amounts may be converted to shares. So it is recommended to keep
     * custom amounts low enough as not to cause overflow when multiplied
     * by `totalSupply`.
     *
     * @param . The address that is depositing into the strategy.
     * @return . The available amount the `_owner` can deposit in terms of `asset`
     */
    function availableDepositLimit(
        address /*_owner*/
    ) public view virtual override returns (uint256) {
        /// We need to be able to both supply and withdraw on deposits.
        if (_isSupplyPaused() || _isBorrowPaused()) return 0;

        uint256 currentAssets = TokenizedStrategy.totalAssets();
        uint256 limit = depositLimit > currentAssets ? depositLimit - currentAssets : 0;

        uint256 maxDeposit = Math.min(_maxCollateralDeposit(), limit);
        uint256 maxBorrow = Math.min(_lenderMaxDeposit(), _maxBorrowAmount());

        // Either the max supply or the max we could borrow / targetLTV.
        return Math.min(maxDeposit, _fromUsd((_toUsd(maxBorrow, borrowToken) * WAD) / _getTargetLTV(), address(asset)));
    }

    /**
     * @notice Gets the max amount of `asset` that can be withdrawn.
     * @dev Defaults to an unlimited amount for any address. But can
     * be overridden by strategists.
     *
     * This function will be called before any withdraw or redeem to enforce
     * any limits desired by the strategist. This can be used for illiquid
     * or sandwichable strategies. It should never be lower than `totalIdle`.
     *
     *   EX:
     *       return TokenIzedStrategy.totalIdle();
     *
     * This does not need to take into account the `_owner`'s share balance
     * or conversion rates from shares to assets.
     *
     * @param . The address that is withdrawing from the strategy.
     * @return . The available amount that can be withdrawn in terms of `asset`
     */
    function availableWithdrawLimit(
        address /*_owner*/
    ) public view virtual override returns (uint256) {
        /// Default liquidity is the balance of collateral + 1 for rounding.
        uint256 liquidity = balanceOfCollateral() + 1;
        uint256 lenderLiquidity = _lenderMaxWithdraw();

        /// If we can't withdraw or supply, set liquidity = 0.
        if (lenderLiquidity < balanceOfLentAssets()) {
            /// Adjust liquidity based on withdrawing the full amount of debt.
            unchecked {
                liquidity = ((_fromUsd(_toUsd(lenderLiquidity, borrowToken), address(asset)) * WAD) / _getTargetLTV());
            }
        }

        return balanceOfAsset() + liquidity;
    }

    /// ----------------- INTERNAL FUNCTIONS SUPPORT ----------------- \\

    /**
     * @notice Adjusts the leverage position of the strategy based on current and target Loan-to-Value (LTV) ratios.
     * @dev All debt and collateral calculations are done in USD terms. LTV values are represented in 1e18 format.
     * @param _amount The amount to be supplied to adjust the leverage position,
     */
    function _leveragePosition(
        uint256 _amount
    ) internal virtual {
        /// Supply the given amount to the strategy.
        // This function internally checks for zero amounts.
        _supplyCollateral(_amount);

        uint256 collateralInUsd = _toUsd(balanceOfCollateral(), address(asset));

        /// Convert debt to USD
        uint256 debtInUsd = _toUsd(balanceOfDebt(), borrowToken);

        /// LTV numbers are always in WAD
        uint256 currentLTV = collateralInUsd > 0 ? (debtInUsd * WAD) / collateralInUsd : 0;
        uint256 targetLTV = _getTargetLTV(); // 70% under default liquidation Threshold

        /// decide in which range we are and act accordingly:
        /// SUBOPTIMAL(borrow) (e.g. from 0 to 70% liqLTV)
        /// HEALTHY(do nothing) (e.g. from 70% to 80% liqLTV)
        /// UNHEALTHY(repay) (e.g. from 80% to 100% liqLTV)
        if (targetLTV > currentLTV) {
            /// SUBOPTIMAL RATIO: our current Loan-to-Value is lower than what we want

            /// we need to take on more debt
            uint256 targetDebtUsd = (collateralInUsd * targetLTV) / WAD;

            uint256 amountToBorrowUsd;
            unchecked {
                amountToBorrowUsd = targetDebtUsd - debtInUsd; // safe bc we checked ratios
            }

            /// convert to borrowToken
            uint256 amountToBorrowBT =
                Math.min(_fromUsd(amountToBorrowUsd, borrowToken), Math.min(_lenderMaxDeposit(), _maxBorrowAmount()));

            /// We want to make sure that the reward apr > borrow apr so we don't report a loss
            /// Borrowing will cause the borrow apr to go up and the rewards apr to go down
            if (getNetBorrowApr(amountToBorrowBT) > getNetRewardApr(amountToBorrowBT)) {
                /// If we would push it over the limit don't borrow anything
                amountToBorrowBT = 0;
            }

            /// Need to have at least the min threshold
            if (amountToBorrowBT > minAmountToBorrow) _borrow(amountToBorrowBT);
        } else if (currentLTV > _getWarningLTV()) {
            /// UNHEALTHY RATIO
            /// we repay debt to set it to targetLTV
            uint256 targetDebtUsd = (targetLTV * collateralInUsd) / WAD;

            /// Withdraw the difference from the Depositor
            _withdrawFromLender(_fromUsd(debtInUsd - targetDebtUsd, borrowToken));

            /// Repay the borrowToken debt.
            _repayTokenDebt();
        }

        // Deposit any loose base token that was borrowed.
        uint256 borrowTokenBalance = balanceOfBorrowToken();
        if (borrowTokenBalance > 0) _lendBorrowToken(borrowTokenBalance);
    }

    /**
     * @notice Liquidates the position to ensure the needed amount while maintaining healthy ratios.
     * @dev All debt, collateral, and needed amounts are calculated in USD. The needed amount is represented in the asset.
     * @param _needed The amount required in the asset.
     */
    function _liquidatePosition(
        uint256 _needed
    ) internal virtual {
        /// Cache balance for withdraw checks
        uint256 balance = balanceOfAsset();

        /// We first repay whatever we need to repay to keep healthy ratios
        _withdrawFromLender(_calculateAmountToRepay(_needed));

        /// we repay the borrowToken debt with the amount withdrawn from the vault
        _repayTokenDebt();

        // Withdraw as much as we can up to the amount needed while maintaining a health ltv
        _withdrawCollateral(Math.min(_needed, _maxWithdrawal()));

        /// We check if we withdrew less than expected, and we do have not more borrowToken
        /// left AND should harvest or buy borrowToken with asset (potentially realising losses)
        if (
            /// if we didn't get enough
            /// still some debt remaining
            /// but no capital to repay
            /// And the leave debt flag is false.
            _needed > balanceOfAsset() - balance && balanceOfDebt() > 0 && balanceOfLentAssets() == 0
                && !leaveDebtBehind
        ) {
            /// using this part of code may result in losses but it is necessary to unlock full collateral
            /// in case of wind down. This should only occur when depleting the strategy so we buy the full
            /// amount of our remaining debt. We buy borrowToken first with available rewards then with asset.
            _buyBorrowToken();

            /// we repay debt to actually unlock collateral
            /// after this, balanceOfDebt should be 0
            _repayTokenDebt();

            /// then we try withdraw once more
            /// still withdraw with target LTV since management can potentially save any left over manually
            _withdrawCollateral(_maxWithdrawal());
        }
    }

    /**
     * @notice Calculates max amount that can be withdrawn while maintaining healthy LTV ratio
     * @dev Considers current collateral and debt amounts
     * @return The max amount of collateral available for withdrawal
     */
    function _maxWithdrawal() internal view virtual returns (uint256) {
        uint256 collateral = balanceOfCollateral();
        uint256 debt = balanceOfDebt();

        /// If there is no debt we can withdraw everything
        if (debt == 0) return collateral;

        uint256 debtInUsd = _toUsd(debt, borrowToken);

        /// What we need to maintain a health LTV
        uint256 neededCollateral = _fromUsd((debtInUsd * WAD) / _getTargetLTV(), address(asset));

        /// We need more collateral so we cant withdraw anything
        if (neededCollateral > collateral) return 0;

        /// Return the difference in terms of asset
        unchecked {
            return collateral - neededCollateral;
        }
    }

    /**
     * @notice Calculates amount of debt to repay to maintain healthy LTV ratio
     * @dev Considers target LTV, amount being withdrawn, and current collateral/debt
     * @param amount The withdrawal amount
     * @return The amount of debt to repay
     */
    function _calculateAmountToRepay(
        uint256 amount
    ) internal view virtual returns (uint256) {
        if (amount == 0) return 0;
        uint256 collateral = balanceOfCollateral();
        /// To unlock all collateral we must repay all the debt
        if (amount >= collateral) return balanceOfDebt();

        /// We check if the collateral that we are withdrawing leaves us in a risky range, we then take action
        uint256 newCollateralUsd = _toUsd(collateral - amount, address(asset));

        uint256 targetD

Tags:
ERC20, Multisig, Mintable, Swap, Liquidity, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x6dec370efa894d48d8c55012b0cd6f3c1c7c4616|verified:true|block:23748052|tx:0xd64bb3d2c6a0d2f1d2bc2a6154c955b9c96521b141a673ca8e972c22ffe1501e|first_check:1762530252

Submitted on: 2025-11-07 16:44:16

Comments

Log in to comment.

No comments yet.