LiquityV2DebtInFrontTrigger

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;









contract MainnetLiquityV2Addresses {
    address internal constant BOLD_ADDR = 0x6440f144b7e50D6a8439336510312d2F54beB01D;
    address internal constant MULTI_TROVE_GETTER_ADDR = 0xFA61dB085510C64B83056Db3A7Acf3b6f631D235;
    address internal constant WETH_MARKET_ADDR = 0x20F7C9ad66983F6523a0881d0f82406541417526;
    address internal constant WSTETH_MARKET_ADDR = 0x8d733F7ea7c23Cbea7C613B6eBd845d46d3aAc54;
    address internal constant RETH_MARKET_ADDR = 0x6106046F031a22713697e04C08B330dDaf3e8789;
}






contract DSMath {
    function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x + y;
    }

    function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x - y;
    }

    function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x * y;
    }

    function div(uint256 x, uint256 y) internal pure returns (uint256 z) {
        return x / y;
    }

    function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
        return x <= y ? x : y;
    }

    function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
        return x >= y ? x : y;
    }

    function imin(int256 x, int256 y) internal pure returns (int256 z) {
        return x <= y ? x : y;
    }

    function imax(int256 x, int256 y) internal pure returns (int256 z) {
        return x >= y ? x : y;
    }

    uint256 constant WAD = 10**18;
    uint256 constant RAY = 10**27;

    function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = add(mul(x, y), WAD / 2) / WAD;
    }

    function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = add(mul(x, y), RAY / 2) / RAY;
    }

    function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = add(mul(x, WAD), y / 2) / y;
    }

    function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = add(mul(x, RAY), y / 2) / y;
    }

    // This famous algorithm is called "exponentiation by squaring"
    // and calculates x^n with x as fixed-point and n as regular unsigned.
    //
    // It's O(log n), instead of O(n) for naive repeated multiplication.
    //
    // These facts are why it works:
    //
    //  If n is even, then x^n = (x^2)^(n/2).
    //  If n is odd,  then x^n = x * x^(n-1),
    //   and applying the equation for even x gives
    //    x^n = x * (x^2)^((n-1) / 2).
    //
    //  Also, EVM division is flooring and
    //    floor[(n-1) / 2] = floor[n / 2].
    //
    function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
        z = n % 2 != 0 ? x : RAY;

        for (n /= 2; n != 0; n /= 2) {
            x = rmul(x, x);

            if (n % 2 != 0) {
                z = rmul(z, x);
            }
        }
    }
}







interface IAddressesRegistry {
    function CCR() external view returns (uint256);
    function SCR() external view returns (uint256);
    function MCR() external view returns (uint256);
    function BCR() external view returns (uint256);
    function LIQUIDATION_PENALTY_SP() external view returns (uint256);
    function LIQUIDATION_PENALTY_REDISTRIBUTION() external view returns (uint256);
    function WETH() external view returns (address);
    function troveNFT() external view returns (address);
    function collToken() external view returns (address);
    function boldToken() external view returns (address);
    function borrowerOperations() external view returns (address);
    function troveManager() external view returns (address);
    function stabilityPool() external view returns (address);
    function activePool() external view returns (address);
    function defaultPool() external view returns (address);
    function sortedTroves() external view returns (address);
    function collSurplusPool() external view returns (address);
    function hintHelpers() external view returns (address);
    function priceFeed() external view returns (address);
    function gasPoolAddress() external view returns (address);
}







interface IBorrowerOperations {
    function CCR() external view returns (uint256);
    function MCR() external view returns (uint256);
    function SCR() external view returns (uint256);

    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 returns (uint256);

    struct OpenTroveAndJoinInterestBatchManagerParams {
        address owner;
        uint256 ownerIndex;
        uint256 collAmount;
        uint256 boldAmount;
        uint256 upperHint;
        uint256 lowerHint;
        address interestBatchManager;
        uint256 maxUpfrontFee;
        address addManager;
        address removeManager;
        address receiver;
    }

    function openTroveAndJoinInterestBatchManager(OpenTroveAndJoinInterestBatchManagerParams calldata _params)
        external
        returns (uint256);

    function addColl(uint256 _troveId, uint256 _ETHAmount) external;

    function withdrawColl(uint256 _troveId, uint256 _amount) external;

    function withdrawBold(uint256 _troveId, uint256 _amount, uint256 _maxUpfrontFee) external;

    function repayBold(uint256 _troveId, uint256 _amount) external;

    function closeTrove(uint256 _troveId) external;

    function adjustTrove(
        uint256 _troveId,
        uint256 _collChange,
        bool _isCollIncrease,
        uint256 _debtChange,
        bool isDebtIncrease,
        uint256 _maxUpfrontFee
    ) external;

    function adjustZombieTrove(
        uint256 _troveId,
        uint256 _collChange,
        bool _isCollIncrease,
        uint256 _boldChange,
        bool _isDebtIncrease,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) external;

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

    function applyPendingDebt(uint256 _troveId, uint256 _lowerHint, uint256 _upperHint) external;

    function onLiquidateTrove(uint256 _troveId) external;

    function claimCollateral() external;

    function hasBeenShutDown() external view returns (bool);
    function shutdown() external;
    function shutdownFromOracleFailure(address _failedOracleAddr) external;

    function checkBatchManagerExists(address _batchMananger) external view returns (bool);

    // -- individual delegation --
    struct InterestIndividualDelegate {
        address account;
        uint128 minInterestRate;
        uint128 maxInterestRate;
    }

    function getInterestIndividualDelegateOf(uint256 _troveId)
        external
        view
        returns (InterestIndividualDelegate memory);
    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
    ) external;
    function removeInterestIndividualDelegate(uint256 _troveId) external;

    // -- batches --
    struct InterestBatchManager {
        uint128 minInterestRate;
        uint128 maxInterestRate;
        uint256 minInterestRateChangePeriod;
    }

    function registerBatchManager(
        uint128 minInterestRate,
        uint128 maxInterestRate,
        uint128 currentInterestRate,
        uint128 fee,
        uint128 minInterestRateChangePeriod
    ) external;
    function lowerBatchManagementFee(uint256 _newAnnualFee) external;
    function setBatchManagerAnnualInterestRate(
        uint128 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) external;
    function interestBatchManagerOf(uint256 _troveId) external view returns (address);
    function getInterestBatchManager(address _account) external view returns (InterestBatchManager memory);
    function setInterestBatchManager(
        uint256 _troveId,
        address _newBatchManager,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) external;
    function removeFromBatch(
        uint256 _troveId,
        uint256 _newAnnualInterestRate,
        uint256 _upperHint,
        uint256 _lowerHint,
        uint256 _maxUpfrontFee
    ) external;
    function switchBatchManager(
        uint256 _troveId,
        uint256 _removeUpperHint,
        uint256 _removeLowerHint,
        address _newBatchManager,
        uint256 _addUpperHint,
        uint256 _addLowerHint,
        uint256 _maxUpfrontFee
    ) external;

    function getEntireBranchColl() external view returns (uint);

    function getEntireBranchDebt() external view returns (uint);
}







interface ISortedTroves {
    // -- Mutating functions (permissioned) --
    function insert(uint256 _id, uint256 _annualInterestRate, uint256 _prevId, uint256 _nextId) external;
    function insertIntoBatch(
        uint256 _troveId,
        address _batchId,
        uint256 _annualInterestRate,
        uint256 _prevId,
        uint256 _nextId
    ) external;

    function remove(uint256 _id) external;
    function removeFromBatch(uint256 _id) external;

    function reInsert(uint256 _id, uint256 _newAnnualInterestRate, uint256 _prevId, uint256 _nextId) external;
    function reInsertBatch(address _id, uint256 _newAnnualInterestRate, uint256 _prevId, uint256 _nextId) external;

    // -- View functions --

    function contains(uint256 _id) external view returns (bool);
    function isBatchedNode(uint256 _id) external view returns (bool);
    function isEmptyBatch(address _id) external view returns (bool);

    function isEmpty() external view returns (bool);
    function getSize() external view returns (uint256);

    function getFirst() external view returns (uint256);
    function getLast() external view returns (uint256);
    function getNext(uint256 _id) external view returns (uint256);
    function getPrev(uint256 _id) external view returns (uint256);

    function validInsertPosition(uint256 _annualInterestRate, uint256 _prevId, uint256 _nextId)
        external
        view
        returns (bool);
    function findInsertPosition(uint256 _annualInterestRate, uint256 _prevId, uint256 _nextId)
        external
        view
        returns (uint256, uint256);

    // Public state variable getters
    function size() external view returns (uint256);
    function nodes(uint256 _id) external view returns (uint256 nextId, uint256 prevId, address batchId, bool exists);
    function batches(address _id) external view returns (uint256 head, uint256 tail);
}







interface IStabilityPool {
    /*  provideToSP():
    * - Calculates depositor's Coll gain
    * - Calculates the compounded deposit
    * - Increases deposit, and takes new snapshots of accumulators P and S
    * - Sends depositor's accumulated Coll gains to depositor
    */
    function provideToSP(uint256 _amount, bool _doClaim) external;

    /*  withdrawFromSP():
    * - Calculates depositor's Coll gain
    * - Calculates the compounded deposit
    * - Sends the requested BOLD withdrawal to depositor
    * - (If _amount > userDeposit, the user withdraws all of their compounded deposit)
    * - Decreases deposit by withdrawn amount and takes new snapshots of accumulators P and S
    */
    function withdrawFromSP(uint256 _amount, bool doClaim) external;

    function claimAllCollGains() external;

    /*
     * Initial checks:
     * - Caller is TroveManager
     * ---
     * Cancels out the specified debt against the Bold contained in the Stability Pool (as far as possible)
     * and transfers the Trove's collateral from ActivePool to StabilityPool.
     * Only called by liquidation functions in the TroveManager.
     */
    function offset(uint256 _debt, uint256 _coll) external;

    function deposits(address _depositor) external view returns (uint256 initialValue);
    function stashedColl(address _depositor) external view returns (uint256);

    /*
     * Returns the total amount of Coll held by the pool, accounted in an internal variable instead of `balance`,
     * to exclude edge cases like Coll received from a self-destruct.
     */
    function getCollBalance() external view returns (uint256);

    /*
     * Returns Bold held in the pool. Changes when users deposit/withdraw, and when Trove debt is offset.
     */
    function getTotalBoldDeposits() external view returns (uint256);

    function getYieldGainsOwed() external view returns (uint256);
    function getYieldGainsPending() external view returns (uint256);

    /*
     * Calculates the Coll gain earned by the deposit since its last snapshots were taken.
     */
    function getDepositorCollGain(address _depositor) external view returns (uint256);

    /*
     * Calculates the BOLD yield gain earned by the deposit since its last snapshots were taken.
     */
    function getDepositorYieldGain(address _depositor) external view returns (uint256);

    /*
     * Calculates what `getDepositorYieldGain` will be if interest is minted now.
     */
    function getDepositorYieldGainWithPending(address _depositor) external view returns (uint256);

    /*
     * Return the user's compounded deposit.
     */
    function getCompoundedBoldDeposit(address _depositor) external view returns (uint256);

    function epochToScaleToS(uint128 _epoch, uint128 _scale) external view returns (uint256);

    function epochToScaleToB(uint128 _epoch, uint128 _scale) external view returns (uint256);

    function P() external view returns (uint256);
    function currentScale() external view returns (uint128);
    function currentEpoch() external view returns (uint128);
}







interface ITroveManager {
    enum Status {
        nonExistent,
        active,
        closedByOwner,
        closedByLiquidation,
        zombie
    }

    struct LatestTroveData {
        uint256 entireDebt;
        uint256 entireColl;
        uint256 redistBoldDebtGain;
        uint256 redistCollGain;
        uint256 accruedInterest;
        uint256 recordedDebt;
        uint256 annualInterestRate;
        uint256 weightedRecordedDebt;
        uint256 accruedBatchManagementFee;
        uint256 lastInterestRateAdjTime;
    }

    struct LatestBatchData {
        uint256 entireDebtWithoutRedistribution;
        uint256 entireCollWithoutRedistribution;
        uint256 accruedInterest;
        uint256 recordedDebt;
        uint256 annualInterestRate;
        uint256 weightedRecordedDebt;
        uint256 annualManagementFee;
        uint256 accruedManagementFee;
        uint256 weightedRecordedBatchManagementFee;
        uint256 lastDebtUpdateTime;
        uint256 lastInterestRateAdjTime;
    }


    function Troves(uint256 _id)
        external
        view
        returns (
            uint256 debt,
            uint256 coll,
            uint256 stake,
            Status status,
            uint64 arrayIndex,
            uint64 lastDebtUpdateTime,
            uint64 lastInterestRateAdjTime,
            uint256 annualInterestRate,
            address interestBatchManager,
            uint256 batchDebtShares
        );

    function shutdownTime() external view returns (uint256);
    function troveNFT() external view returns (address);
    function getLatestTroveData(uint256 _troveId) external view returns (LatestTroveData memory);
    function getCurrentICR(uint256 _troveId, uint256 _price) external view returns (uint256);
    function getTroveStatus(uint256 _troveId) external view returns (Status);
    function getTroveAnnualInterestRate(uint256 _troveId) external view returns (uint256);
    function getLatestBatchData(address _batchAddress) external view returns (LatestBatchData memory);
}













contract LiquityV2Helper is MainnetLiquityV2Addresses, DSMath {

    /// @notice Cooldown period for interest rate adjustments (7 days)
    uint256 constant INTEREST_RATE_ADJ_COOLDOWN = 7 days;

    /// @notice Maximum number of iterations to get the debt in front for a current trove branch
    uint256 internal constant MAX_ITERATIONS = 1000;

    // Amount of ETH to be locked in gas pool on opening troves
    uint256 constant ETH_GAS_COMPENSATION = 0.0375 ether;

    // Minimum amount of net Bold debt a trove must have
    uint256 constant MIN_DEBT = 2000e18;

    // collateral indexes for different branches (markets)
    uint256 constant WETH_COLL_INDEX = 0;
    uint256 constant WSTETH_COLL_INDEX = 1;
    uint256 constant RETH_COLL_INDEX = 2;

    /// @notice Error thrown when an invalid market address is provided
    error InvalidMarketAddress();

    /// @notice Helper struct containing the total debt and unbacked debt of a single market
    /// @dev totalDebt is the total bold debt of the market
    /// @dev unbackedDebt is the unbacked bold debt of the market. Diff between total debt and stability pool bold deposits
    struct Market {
        uint256 totalDebt;
        uint256 unbackedDebt;
    }

    /// @notice Helper struct containing the current market and other markets data.
    /// @notice Used for estimating the redemption amounts per market
    /// @dev current is the current market we are calculating the debt in front for
    /// @dev otherMarkets are the 2 other markets
    struct Markets {
        Market current;
        Market[] otherMarkets;
    }

    /// @notice Gets the debt in front for a given market and trove
    /// @param _market address of the market (a.k.a. branch)
    /// @param _trove id of the trove
    /// @return debtInFront debt in front of the trove
    /// @dev This function estimates the total real debt in front of a given trove.
    /// Because redemptions are routed through every branch, the total debt in front 
    /// is usually higher than the debt of the troves preceding the current trove in its given branch.
    /// General equation:
    /// X * branchRedeemPercentage = branchDebtInFront
    /// X * (totalDebtOrUnbackedDebtOnBranch / totalDebtOrUnbackedDebt) = branchDebtInFront
    /// X = branchDebtInFront * totalDebtOrUnbackedDebt / totalDebtOrUnbackedDebtOnBranch
    /// Where X is the estimated redemption amount for which all debt in front of the trove in its branch will be redeemed.
    function getDebtInFront(
        address _market,
        uint256 _trove
    ) public view returns (uint256) {
        (uint256 ethTotalDebt, uint256 ethUnbackedDebt) = _getTotalAndUnbackedDebt(WETH_MARKET_ADDR);
        (uint256 wstEthTotalDebt, uint256 wstEthUnbackedDebt) = _getTotalAndUnbackedDebt(WSTETH_MARKET_ADDR);
        (uint256 rEthTotalDebt, uint256 rEthUnbackedDebt) = _getTotalAndUnbackedDebt(RETH_MARKET_ADDR);

        uint256 totalUnbackedDebt = ethUnbackedDebt + wstEthUnbackedDebt + rEthUnbackedDebt;
        uint256 totalDebt = ethTotalDebt + wstEthTotalDebt + rEthTotalDebt;
        uint256 branchDebtInFront = _getTroveDebtInFrontForCurrentBranch(_market, _trove);

        Markets memory markets = _getMarketsData(
            _market,
            ethTotalDebt,
            ethUnbackedDebt,
            wstEthTotalDebt,
            wstEthUnbackedDebt,
            rEthTotalDebt,
            rEthUnbackedDebt
        );

        // Sanity check to avoid division by 0. Highly unlikely to ever happen.
        if (markets.current.totalDebt == 0) return 0;

        // CASE 1: Current branch has 0 unbacked debt
        // When totalUnbackedDebt is 0, redemptions will be proportional with the branch size and not to unbacked debt.
        // When unbacked debt is 0 for some branch, next redemption call won't touch that branch, so in order to estimate total debt in front we will:
        // - First add up all the unbacked debt from other branches, as that will be the only debt that will be redeemed on the fist redemption call
        // - Perform split the same way as we do when totalUnbackedDebt == 0, this would represent the second call to the redemption function
        if (markets.current.unbackedDebt == 0) {
            // If the branch debt in front is 0, it means that all debt in front is unbacked debt from other branches.
            if (branchDebtInFront == 0) {
                return markets.otherMarkets[0].unbackedDebt + markets.otherMarkets[1].unbackedDebt;
            }

            // 1. First redemption call:
            // - add up all the unbacked debt from other branches
            // - remove the unbacked debt from total debt as it will be redeemed on the first call
            // - update the total debt of other branches
            uint256 redeemAmountFromFirstCall = markets.otherMarkets[0].unbackedDebt + markets.otherMarkets[1].unbackedDebt;
            totalDebt -= redeemAmountFromFirstCall;
            markets.otherMarkets[0].totalDebt -= markets.otherMarkets[0].unbackedDebt;
            markets.otherMarkets[1].totalDebt -= markets.otherMarkets[1].unbackedDebt;

            // 2. Second redemption call:
            // Perform the split by total debt because there is no more unbacked debt to redeem
            uint256 estimatedRedemptionAmount = branchDebtInFront * totalDebt / markets.current.totalDebt;
            uint256[] memory redemptionAmounts = _calculateRedemptionAmounts(
                estimatedRedemptionAmount,
                totalDebt,
                markets,
                false // isTotalUnbacked = false. Proportional to total debt
            );
            uint256 redeemAmountFromSecondCall = branchDebtInFront + redemptionAmounts[0] + redemptionAmounts[1];

            return redeemAmountFromFirstCall + redeemAmountFromSecondCall;
        }

        // CASE 2: Current branch has unbacked debt
        uint256 estimatedRedemptionAmount = branchDebtInFront * totalUnbackedDebt / markets.current.unbackedDebt;
        uint256[] memory redemptionAmounts = _calculateRedemptionAmounts(
            estimatedRedemptionAmount,
            totalUnbackedDebt,
            markets,
            true // isTotalUnbacked = true. Proportional to total unbacked debt
        );
        return branchDebtInFront + redemptionAmounts[0] + redemptionAmounts[1];
    }

    /*//////////////////////////////////////////////////////////////
                            INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/
    function _calculateRedemptionAmounts(
        uint256 _estimatedRedemptionAmount,
        uint256 _total,
        Markets memory _markets,
        bool _isTotalUnbacked
    ) internal pure returns (uint256[] memory redemptionAmounts) {
        redemptionAmounts = new uint256[](2);
        for (uint256 i = 0; i < 2; ++i) {
            uint256 branchProportion = _isTotalUnbacked
                ? _markets.otherMarkets[i].unbackedDebt
                : _markets.otherMarkets[i].totalDebt;
            redemptionAmounts[i] = min(
                branchProportion * _estimatedRedemptionAmount / _total,
                _markets.otherMarkets[i].totalDebt
            );
        }
    }

    function _getTotalAndUnbackedDebt(
        address _market
    ) internal view returns (uint256 branchDebt, uint256 unbackedBold) {
        IAddressesRegistry registry = IAddressesRegistry(_market);
        branchDebt = IBorrowerOperations(registry.borrowerOperations()).getEntireBranchDebt();
        uint256 boldDeposits = IStabilityPool(registry.stabilityPool()).getTotalBoldDeposits();

        unbackedBold = branchDebt > boldDeposits ? branchDebt - boldDeposits : 0;
    }

    function _getTroveDebtInFrontForCurrentBranch(
        address _market,
        uint256 _troveId
    ) public view returns (uint256 debt) {
        ITroveManager troveManager = ITroveManager(IAddressesRegistry(_market).troveManager());
        ISortedTroves sortedTroves = ISortedTroves(IAddressesRegistry(_market).sortedTroves());

        uint256 next = _troveId;
        for (uint256 i = 0; i < MAX_ITERATIONS; ++i) {
            next = sortedTroves.getNext(next);
            if (next == 0) return debt;
            debt += _getTroveTotalDebt(troveManager, next);
        }
    }

    function _getTroveTotalDebt(
        ITroveManager _troveManager,
        uint256 _troveId
    ) internal view returns (uint256 debt) {
        ITroveManager.LatestTroveData memory latestTroveData = _troveManager.getLatestTroveData(_troveId);
        debt = latestTroveData.entireDebt;
    }

    function _getMarketsData(
        address _currentMarket,
        uint256 _ethTotalDebt,
        uint256 _ethUnbackedDebt,
        uint256 _wstEthTotalDebt,
        uint256 _wstEthUnbackedDebt,
        uint256 _rEthTotalDebt,
        uint256 _rEthUnbackedDebt
    ) internal pure returns (Markets memory retVal) {
        if (_currentMarket == WETH_MARKET_ADDR) {
            retVal.current = Market(_ethTotalDebt, _ethUnbackedDebt);
            retVal.otherMarkets = new Market[](2);
            retVal.otherMarkets[0] = Market(_wstEthTotalDebt, _wstEthUnbackedDebt);
            retVal.otherMarkets[1] = Market(_rEthTotalDebt, _rEthUnbackedDebt);
        } else if (_currentMarket == WSTETH_MARKET_ADDR) {
            retVal.current = Market(_wstEthTotalDebt, _wstEthUnbackedDebt);
            retVal.otherMarkets = new Market[](2);
            retVal.otherMarkets[0] = Market(_ethTotalDebt, _ethUnbackedDebt);
            retVal.otherMarkets[1] = Market(_rEthTotalDebt, _rEthUnbackedDebt);
        } else if (_currentMarket == RETH_MARKET_ADDR) {
            retVal.current = Market(_rEthTotalDebt, _rEthUnbackedDebt);
            retVal.otherMarkets = new Market[](2);
            retVal.otherMarkets[0] = Market(_ethTotalDebt, _ethUnbackedDebt);
            retVal.otherMarkets[1] = Market(_wstEthTotalDebt, _wstEthUnbackedDebt);
        } else {
            revert InvalidMarketAddress();
        }
    }
}







contract MainnetAuthAddresses {
    address internal constant ADMIN_VAULT_ADDR = 0xCCf3d848e08b94478Ed8f46fFead3008faF581fD;
    address internal constant DSGUARD_FACTORY_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7;
    address internal constant ADMIN_ADDR = 0x25eFA336886C74eA8E282ac466BdCd0199f85BB9; // USED IN ADMIN VAULT CONSTRUCTOR
    address internal constant PROXY_AUTH_ADDRESS = 0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70;
    address internal constant MODULE_AUTH_ADDRESS = 0x7407974DDBF539e552F1d051e44573090912CC3D;
}







contract AuthHelper is MainnetAuthAddresses {
}








contract AdminVault is AuthHelper {
    address public owner;
    address public admin;

    error SenderNotAdmin();

    constructor() {
        owner = msg.sender;
        admin = ADMIN_ADDR;
    }

    /// @notice Admin is able to change owner
    /// @param _owner Address of new owner
    function changeOwner(address _owner) public {
        if (admin != msg.sender){
            revert SenderNotAdmin();
        }
        owner = _owner;
    }

    /// @notice Admin is able to set new admin
    /// @param _admin Address of multisig that becomes new admin
    function changeAdmin(address _admin) public {
        if (admin != msg.sender){
            revert SenderNotAdmin();
        }
        admin = _admin;
    }

}







interface IERC20 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint256 digits);
    function totalSupply() external view returns (uint256 supply);

    function balanceOf(address _owner) external view returns (uint256 balance);

    function transfer(address _to, uint256 _value) external returns (bool success);

    function transferFrom(
        address _from,
        address _to,
        uint256 _value
    ) external returns (bool success);

    function approve(address _spender, uint256 _value) external returns (bool success);

    function allowance(address _owner, address _spender) external view returns (uint256 remaining);

    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}







library Address {
    //insufficient balance
    error InsufficientBalance(uint256 available, uint256 required);
    //unable to send value, recipient may have reverted
    error SendingValueFail();
    //insufficient balance for call
    error InsufficientBalanceForCall(uint256 available, uint256 required);
    //call to non-contract
    error NonContractCall();
    
    function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            codehash := extcodehash(account)
        }
        return (codehash != accountHash && codehash != 0x0);
    }

    function sendValue(address payable recipient, uint256 amount) internal {
        uint256 balance = address(this).balance;
        if (balance < amount){
            revert InsufficientBalance(balance, amount);
        }

        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{value: amount}("");
        if (!(success)){
            revert SendingValueFail();
        }
    }

    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return _functionCallWithValue(target, data, 0, errorMessage);
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return
            functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        uint256 balance = address(this).balance;
        if (balance < value){
            revert InsufficientBalanceForCall(balance, value);
        }
        return _functionCallWithValue(target, data, value, errorMessage);
    }

    function _functionCallWithValue(
        address target,
        bytes memory data,
        uint256 weiValue,
        string memory errorMessage
    ) private returns (bytes memory) {
        if (!(isContract(target))){
            revert NonContractCall();
        }

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{value: weiValue}(data);
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}











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 Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
     * 0 before setting it to a non-zero value.
     */
    function safeApprove(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 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(token).code.length > 0;
    }
}











contract AdminAuth is AuthHelper {
    using SafeERC20 for IERC20;

    AdminVault public constant adminVault = AdminVault(ADMIN_VAULT_ADDR);

    error SenderNotOwner();
    error SenderNotAdmin();

    modifier onlyOwner() {
        if (adminVault.owner() != msg.sender){
            revert SenderNotOwner();
        }
        _;
    }

    modifier onlyAdmin() {
        if (adminVault.admin() != msg.sender){
            revert SenderNotAdmin();
        }
        _;
    }

    /// @notice withdraw stuck funds
    function withdrawStuckFunds(address _token, address _receiver, uint256 _amount) public onlyOwner {
        if (_token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
            payable(_receiver).transfer(_amount);
        } else {
            IERC20(_token).safeTransfer(_receiver, _amount);
        }
    }

    /// @notice Destroy the contract
    /// @dev Deprecated method, selfdestruct will soon just send eth
    function kill() public onlyAdmin {
        selfdestruct(payable(msg.sender));
    }
}







contract MainnetTriggerAddresses {
    address public constant UNISWAP_V3_NONFUNGIBLE_POSITION_MANAGER = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88;
    address public constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
    address public constant MCD_PRICE_VERIFIER = 0xeAa474cbFFA87Ae0F1a6f68a3aBA6C77C656F72c;
    address public constant TRANSIENT_STORAGE = 0x2F7Ef2ea5E8c97B8687CA703A0e50Aa5a49B7eb2;
    address public constant TRANSIENT_STORAGE_CANCUN = 0x0304E27cccE28bAB4d78C6cb7AfD4cd01c87c1e4;
}







contract TriggerHelper is MainnetTriggerAddresses {
}






abstract contract ITrigger {
    function isTriggered(bytes memory, bytes memory) public virtual returns (bool);
    function isChangeable() public virtual returns (bool);
    function changedSubData(bytes memory) public virtual returns (bytes memory);
}











contract LiquityV2DebtInFrontTrigger is
    ITrigger,
    AdminAuth,
    TriggerHelper,
    LiquityV2Helper
{

    /// @param market address of the market
    /// @param troveId id of the trove
    /// @param debtInFrontMin minimum debt in front, below which the trigger will be triggered
    struct SubParams {
        address market;
        uint256 troveId;
        uint256 debtInFrontMin;
    }

    /// @notice Checks if the debt in front of the trove is below the minimum debt in front
    /// @param _subData bytes encoded SubParams struct
    /// @return bool true if the debt in front of the trove is below the minimum debt in front
    function isTriggered(bytes memory, bytes memory _subData) public view override returns (bool) {
        SubParams memory triggerSubData = parseSubInputs(_subData);

        uint256 debtInFront = getDebtInFront(triggerSubData.market, triggerSubData.troveId);

        return debtInFront < triggerSubData.debtInFrontMin;
    }

    function parseSubInputs(bytes memory _subData) public pure returns (SubParams memory params) {
        params = abi.decode(_subData, (SubParams));
    }

    function changedSubData(bytes memory _subData) public pure override returns (bytes memory) {}

    function isChangeable() public pure override returns (bool) {
        return false;
    }
}

Tags:
ERC20, Multisig, Yield, Multi-Signature|addr:0xd8ed394cb77f960ee095379a17cb58d175b75c52|verified:true|block:23382875|tx:0x74ff58200b52b734e14fed9df26ab7da360fd164583d08461f6ad4ed2b4a50f7|first_check:1758113264

Submitted on: 2025-09-17 14:47:46

Comments

Log in to comment.

No comments yet.