BridgeHookTarget

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/TwyneFactory/BridgeHookTarget.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.28;

import {CollateralVaultFactory} from "src/TwyneFactory/CollateralVaultFactory.sol";
import {CollateralVaultBase} from "src/twyne/CollateralVaultBase.sol";
import {IErrors} from "src/interfaces/IErrors.sol";

/// @title BridgeHookTarget
/// @notice To contact the team regarding security matters, visit https://twyne.xyz/security
contract BridgeHookTarget is IErrors {
    CollateralVaultFactory immutable collateralVaultFactory;

    constructor(address _collateralVaultFactory) {
        collateralVaultFactory = CollateralVaultFactory(_collateralVaultFactory);
    }

    function isHookTarget() external pure returns (bytes4) {
        return this.isHookTarget.selector;
    }

    function borrow(uint /*amount*/, address receiver) external view {
        require(collateralVaultFactory.isCollateralVault(receiver), ReceiverNotCollateralVault());
    }

    function liquidate(address violator, address /*collateral*/, uint /*repayAssets*/, uint /*minYieldBalance*/) external view {
        require(collateralVaultFactory.isCollateralVault(violator), ViolatorNotCollateralVault());
        require(CollateralVaultBase(violator).borrower() == address(0), NotExternallyLiquidated());
    }

    fallback() external {
        revert T_OperationDisabled();
    }
}
"
    },
    "src/TwyneFactory/CollateralVaultFactory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.28;

import {OwnableUpgradeable, ContextUpgradeable} from "openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {PausableUpgradeable} from "openzeppelin-upgradeable/utils/PausableUpgradeable.sol";
import {UUPSUpgradeable} from "openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {BeaconProxy} from "openzeppelin-contracts/proxy/beacon/BeaconProxy.sol";
import {CollateralVaultBase} from "src/twyne/CollateralVaultBase.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {VaultManager} from "src/twyne/VaultManager.sol";
import {IEVault} from "euler-vault-kit/EVault/IEVault.sol";
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";
import {IErrors} from "src/interfaces/IErrors.sol";
import {IEvents} from "src/interfaces/IEvents.sol";

/// @title CollateralVaultFactory
/// @notice To contact the team regarding security matters, visit https://twyne.xyz/security
contract CollateralVaultFactory is UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable, EVCUtil, IErrors, IEvents {
    mapping(address targetVault => address beacon) public collateralVaultBeacon;
    mapping(address => bool) public isCollateralVault;

    /// @dev collateralVaults that are deployed by borrower or liquidated by borrower.
    /// vault may not be currently owned by borrower.
    mapping(address borrower => address[] collateralVaults) public collateralVaults;

    VaultManager public vaultManager;
    mapping(address => uint nonce) public nonce;

    uint[50] private __gap;

    constructor(address _evc) EVCUtil(_evc) {
        _disableInitializers();
    }

    /// @notice Initialize the CollateralVaultFactory
    /// @param _owner Address of the initial owner
    function initialize(address _owner) external initializer {
        __Ownable_init(_owner);
        __Pausable_init();
        __UUPSUpgradeable_init();
    }

    /// @dev override required by UUPSUpgradeable
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /// @dev increment the version for proxy upgrades
    function version() external pure returns (uint) {
        return 1;
    }

    function getCollateralVaults(address borrower) external view returns (address[] memory) {
        return collateralVaults[borrower];
    }

    /// @notice Set a new vault manager address. Governance-only.
    function setVaultManager(address _manager) external onlyOwner {
        vaultManager = VaultManager(payable(_manager));
        emit T_SetVaultManager(_manager);
    }

    /// @notice Set a new beacon address for a specific target vault. Governance-only.
    function setBeacon(address targetVault, address beacon) external onlyOwner {
        collateralVaultBeacon[targetVault] = beacon;
        emit T_SetBeacon(targetVault, beacon);
    }

    /// @notice callable only by a collateral vault in the case where it has been liquidated
    function setCollateralVaultLiquidated(address liquidator) external {
        require(isCollateralVault[msg.sender], NotCollateralVault());
        collateralVaults[liquidator].push(msg.sender);
        emit T_SetCollateralVaultLiquidated(msg.sender, liquidator);
    }

    /// @dev pause deposit and borrowing via collateral vault
    function pause(bool p) external onlyOwner {
        p ? _pause() : _unpause();
        emit T_FactoryPause(p);
    }

    function _msgSender() internal view override(ContextUpgradeable, EVCUtil) returns (address) {
        return EVCUtil._msgSender();
    }

    /// @notice This function is called when a borrower wants to deploy a new collateral vault.
    /// @param _asset address of vault asset
    /// @param _targetVault address of the target vault, used for the lookup of the beacon proxy implementation contract
    /// @param _liqLTV user-specified target LTV
    /// @return vault address of the newly created collateral vault
    function createCollateralVault(address _asset, address _targetVault, uint _liqLTV)
        external
        callThroughEVC
        whenNotPaused
        returns (address vault)
    {
        // First validate the input params
        address intermediateVault = vaultManager.getIntermediateVault(_asset);
        // collateral is allowed because the above line will revert if collateral is not recognized
        require(vaultManager.isAllowedTargetVault(intermediateVault, _targetVault), NotIntermediateVault());

        address msgSender = _msgSender();
        vault = address(new BeaconProxy{salt: keccak256(abi.encodePacked(msgSender, nonce[msgSender]++))}(collateralVaultBeacon[_targetVault], ""));
        isCollateralVault[vault] = true;

        collateralVaults[msgSender].push(vault);

        CollateralVaultBase(vault).initialize(IERC20(_asset), msgSender, _liqLTV, vaultManager);
        vaultManager.setOracleResolvedVault(vault, true);
        // Having hardcoded _liquidationLimit here is fine since vault's liquidation by intermediateVault is disabled
        // during normal operation. It's allowed only when vault is externally liquidated and that too it's to settle bad debt.
        vaultManager.setLTV(IEVault(intermediateVault), vault, 1e4, 1e4, 0);

        emit T_CollateralVaultCreated(vault);
    }
}
"
    },
    "src/twyne/CollateralVaultBase.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.28;

import {IErrors} from "src/interfaces/IErrors.sol";
import {IEvents} from "src/interfaces/IEvents.sol";
import {PausableUpgradeable} from "openzeppelin-upgradeable/utils/PausableUpgradeable.sol";
import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Address} from "openzeppelin-contracts/utils/Address.sol";
import {IEVault} from "euler-vault-kit/EVault/IEVault.sol";
import {VaultManager} from "src/twyne/VaultManager.sol";
import {CollateralVaultFactory} from "src/TwyneFactory/CollateralVaultFactory.sol";
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";
import {SafeERC20Lib, IERC20 as IERC20_Euler} from "euler-vault-kit/EVault/shared/lib/SafeERC20Lib.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {ReentrancyGuardUpgradeable} from "openzeppelin-upgradeable/utils/ReentrancyGuardUpgradeable.sol";

/// @title CollateralVaultBase
/// @notice To contact the team regarding security matters, visit https://twyne.xyz/security
/// @notice Provides general vault functionality applicable to any external integration.
/// @dev This isn't a ERC4626 vault, nor does it comply with ERC20 specification.
/// @dev It only supports name, symbol, balanceOf(address) and reverts for all other ERC20 fns.
/// @notice In this contract, the EVC is authenticated before any action that may affect the state of the vault or an account.
/// @notice This is done to ensure that if its EVC calling, the account is correctly authorized.
abstract contract CollateralVaultBase is EVCUtil, ReentrancyGuardUpgradeable, IErrors, IEvents  {
    uint internal constant MAXFACTOR = 1e4;
    address internal constant permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    address public immutable targetVault;

    uint private snapshot;
    uint public totalAssetsDepositedOrReserved;

    address public borrower;
    uint public twyneLiqLTV;
    VaultManager public twyneVaultManager;
    CollateralVaultFactory public collateralVaultFactory;
    IEVault public intermediateVault;
    address private _asset;

    string public constant name = "Collateral Vault";
    string public constant symbol = "CV";

    uint[50] private __gap;

    modifier onlyBorrowerAndNotExtLiquidated() {
        _callThroughEVC();
        require(_msgSender() == borrower, ReceiverNotBorrower());
        require(totalAssetsDepositedOrReserved <= IERC20(asset()).balanceOf(address(this)), ExternallyLiquidated());
        _;
    }

    modifier whenNotPaused() {
        require(!collateralVaultFactory.paused(), PausableUpgradeable.EnforcedPause());
        _;
    }

    /// @notice Reentrancy guard for view functions
    modifier nonReentrantView() virtual {
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }
        _;
    }

    /// @param _evc address of EVC deployed by Twyne
    /// @param _targetVault address of the target vault to borrow from
    constructor(address _evc, address _targetVault) EVCUtil(_evc) {
        targetVault = _targetVault;
    }

    /// @param __asset address of vault asset
    /// @param __borrower address of vault owner
    /// @param __liqLTV user-specified target LTV
    /// @param __vaultManager VaultManager contract address
    function __CollateralVaultBase_init(
        IERC20 __asset,
        address __borrower,
        uint __liqLTV,
        VaultManager __vaultManager
    ) internal onlyInitializing {
        __ReentrancyGuard_init();

        _asset = address(__asset);
        borrower = __borrower;

        twyneVaultManager = __vaultManager;
        collateralVaultFactory = CollateralVaultFactory(msg.sender);
        address _intermediateVault = twyneVaultManager.getIntermediateVault(address(__asset));
        intermediateVault = IEVault(_intermediateVault);

        // checkLiqLTV must happen after targetVault() and asset() return meaningful values
        __vaultManager.checkLiqLTV(__liqLTV, targetVault, address(__asset));
        twyneLiqLTV = __liqLTV;
        SafeERC20.forceApprove(IERC20(__asset), _intermediateVault, type(uint).max); // necessary for EVK repay()
        evc.enableController(address(this), _intermediateVault); // necessary for Twyne EVK borrowing
        evc.enableCollateral(address(this), address(this)); // necessary for Twyne EVK borrowing

        require(address(__asset) == IEVault(_intermediateVault).asset(), AssetMismatch());
    }

    function initialize(
        IERC20 __asset,
        address __borrower,
        uint __liqLTV,
        VaultManager __vaultManager
    ) external virtual;

    ///
    // ERC20-compatible functionality
    ///

    /// @dev balanceOf(address(this)) is used by intermediate vault to check the collateral amount.
    /// This vault borrows from intermediate vault using the vault as the collateral asset.
    /// This means balanceOf(borrower-from-intermediate-vault's-perspective) needs to return the amount
    /// of collateral held by borrower-from-intermediate-vault's-perspective.
    /// On first deposit, this value is the deposit amount. Over time, it decreases due to our siphoning mechanism.
    /// Thus, we return borrower owned collateral.
    /// @param user address
    function balanceOf(address user) external view virtual returns (uint);

    /// @dev This is used by intermediate vault to price the collateral (aka collateral vault).
    /// Since CollateralVault.balanceOf(address(this)) [=shares] returns the amount of asset owned by the borrower,
    /// collateral vault token is 1:1 with asset.
    /// @param shares shares
    function convertToAssets(uint shares) external pure returns (uint) {
        return shares;
    }

    /// @dev transfer, transferFrom, allowance, totalSupply, and approve functions aren't supported.
    /// Calling these functions will revert. IF this vault had to be an ERC20 token,
    /// we would only mint the token to this vault itself, and disable any transfer. This makes
    /// approve functionality useless.
    /// How does intermediate vault transfer the collateral during liquidation?
    /// Intermediate vault is liquidated only when this collateral vault has 0 asset.
    /// Thus, the intermediate vault never needs to transfer collateral.
    fallback() external {
        revert T_CV_OperationDisabled();
    }

    //////////////////////////////////////////

    /// @dev returns implementation version
    function version() external virtual pure returns (uint);

    /// @dev returns collateral vault asset (which is the token of another lending protocol, like an aToken, eToken, etc.)
    function asset() public view returns (address) {
        return _asset;
    }

    ///
    // Target asset functions
    ///

    /// @notice Returns the maximum amount of debt that can be repaid to the external lending protocol
    /// @dev This function must be implemented by derived contracts for specific external protocol integrations
    /// @dev The returned value represents the current total debt (principal + interest) owed by this vault
    /// @dev In the protocol mechanics, this corresponds to B in the mathematical formulation
    /// @return The maximum amount of debt that can be repaid, denominated in the target asset's units
    function maxRepay() public view virtual returns (uint);

    /// @dev collateral vault borrows targetAsset from underlying protocol.
    /// Implementation should make sure targetAsset is whitelisted.
    function _borrow(uint _targetAmount, address _receiver) internal virtual;

    /// @dev Implementation should make sure the correct targetAsset is repaid and the repay action is successful.
    /// Revert otherwise.
    function _repay(uint _targetAmount) internal virtual;

    /// @notice Borrows target assets from the external lending protocol
    /// @dev This function calls the internal _borrow function to handle the protocol-specific borrow logic,
    /// then transfers the target asset from the vault to _receiver.
    /// @param _targetAmount The amount of target asset to borrow
    /// @param _receiver The receiver of the borrowed assets
    function borrow(uint _targetAmount, address _receiver)
        external
        onlyBorrowerAndNotExtLiquidated
        whenNotPaused
        nonReentrant
    {
        createVaultSnapshot();
        _borrow(_targetAmount, _receiver);
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_Borrow(_targetAmount, _receiver);
    }

    /// @notice Repays debt owed to the external lending protocol
    /// @dev If _amount is set to type(uint).max, the entire debt will be repaid
    /// @dev This function transfers the target asset from the caller to the vault, then
    /// calls the internal _repay function to handle the protocol-specific repayment logic
    /// @dev Reverts if attempting to repay more than the current debt
    /// @param _amount The amount of target asset to repay, or type(uint).max for full repayment
    function repay(uint _amount) external onlyBorrowerAndNotExtLiquidated nonReentrant {
        createVaultSnapshot();
        uint _maxRepay = maxRepay();
        if (_amount == type(uint).max) {
            _amount = _maxRepay;
        } else {
            require(_amount <= _maxRepay, RepayingMoreThanMax());
        }

        _repay(_amount);
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireVaultStatusCheck();
        emit T_Repay(_amount);
    }

    ///
    // Intermediate vault functions
    ///

    /// @notice Returns the maximum amount of credit that can be released back to the intermediate vault
    /// @dev This represents the total debt (principal + accumulated interest) owed by this vault to the
    /// intermediate vault from which credit was reserved
    /// @dev In the protocol mechanics, this corresponds to C_LP (credit from LPs) in the mathematical formulation
    /// @dev The value is denominated in the collateral asset
    /// @return uint The maximum amount of collateral asset that can be released back to the intermediate vault
    function maxRelease() public view returns (uint) {
        // this math is the same as EVK's getCurrentOwed() used in repay() to find max repay amount
        return Math.min(intermediateVault.debtOf(address(this)), totalAssetsDepositedOrReserved);
    }

    ///
    // Functions from VaultSimple
    // https://github.com/euler-xyz/evc-playground/blob/master/src/vaults/open-zeppelin/VaultSimple.sol
    ///

    /// @notice Creates a snapshot of the vault state
    /// @dev This function is called before any action that may affect the vault's state.
    function createVaultSnapshot() internal {
        // We delete snapshots on `checkVaultStatus`, which can only happen at the end of the EVC batch. Snapshots are
        // taken before any action is taken on the vault that affects the vault asset records and deleted at the end, so
        // that asset calculations are always based on the state before the current batch of actions.
        if (snapshot == 0) {
            snapshot = 1;
        }
    }

    /// @notice Checks the vault status
    /// @dev This function is called after any action that may affect the vault's state.
    /// @dev Executed as a result of requiring vault status check on the EVC.
    function checkVaultStatus() external onlyEVCWithChecksInProgress returns (bytes4) {
        // sanity check in case the snapshot hasn't been taken
        require(snapshot != 0, SnapshotNotTaken());
        require(!_canLiquidate(), VaultStatusLiquidatable());

        // If the vault has been externally liquidated, any bad debt from intermediate vault
        // has to be settled via intermediateVault.liquidate().
        // Bad debt settlement reduces this debt to 0.
        if (borrower == address(0)) {
            require(intermediateVault.debtOf(address(this)) == 0, BadDebtNotSettled());
        }
        delete snapshot;

        return this.checkVaultStatus.selector;
    }

    ///
    // Asset transfer functions
    ///

    /// @notice Deposits a certain amount of assets.
    /// @param assets The assets to deposit.
    function deposit(uint assets)
        external
        onlyBorrowerAndNotExtLiquidated
        whenNotPaused
        nonReentrant
    {
        createVaultSnapshot();

        SafeERC20Lib.safeTransferFrom(IERC20_Euler(asset()), borrower, address(this), assets, permit2);
        totalAssetsDepositedOrReserved += assets;
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_Deposit(assets);
    }

    /// @notice Deposits a certain amount of underlying asset.
    /// @param underlying The underlying assets to deposit.
    function depositUnderlying(uint underlying)
        external
        onlyBorrowerAndNotExtLiquidated
        whenNotPaused
        nonReentrant
    {
        createVaultSnapshot();
        totalAssetsDepositedOrReserved += _depositUnderlying(underlying);
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_DepositUnderlying(underlying);
    }

    // _depositUnderlying() requires custom implementation per protocol integration
    function _depositUnderlying(uint underlying) internal virtual returns (uint assets);

    /// @notice Deposits airdropped collateral asset.
    /// @dev This is the last step in a 1-click leverage batch.
    function skim() external callThroughEVC whenNotPaused nonReentrant {
        // copied from onlyBorrowerAndNotExtLiquidated modifier to cache balanceOf
        require(_msgSender() == borrower, ReceiverNotBorrower());
        uint balance = IERC20(asset()).balanceOf(address(this));
        uint _totalAssetsDepositedOrReserved = totalAssetsDepositedOrReserved;
        require(_totalAssetsDepositedOrReserved <= balance, ExternallyLiquidated());

        createVaultSnapshot();
        totalAssetsDepositedOrReserved = balance;
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_Skim(balance - _totalAssetsDepositedOrReserved);
    }

    /// @notice Withdraws a certain amount of assets for a receiver.
    /// @param assets Amount of collateral assets to withdraw.
    /// @param receiver The receiver of the withdrawal.
    function withdraw(
        uint assets,
        address receiver
    ) public onlyBorrowerAndNotExtLiquidated nonReentrant {
        createVaultSnapshot();

        uint _totalAssetsDepositedOrReserved = totalAssetsDepositedOrReserved;
        if (assets == type(uint).max) {
            assets = _totalAssetsDepositedOrReserved - maxRelease();
        }

        totalAssetsDepositedOrReserved = _totalAssetsDepositedOrReserved - assets;
        SafeERC20.safeTransfer(IERC20(asset()), receiver, assets);
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_Withdraw(assets, receiver);
    }

    /// @notice Withdraw a certain amount of collateral and transfers collateral asset's underlying asset to receiver.
    /// @param assets Amount of collateral asset to withdraw.
    /// @param receiver The receiver of the redemption.
    /// @return underlying Amount of underlying asset transferred.
    function redeemUnderlying(
        uint assets,
        address receiver
    ) public onlyBorrowerAndNotExtLiquidated nonReentrant returns (uint underlying) {
        createVaultSnapshot();

        uint _totalAssetsDepositedOrReserved = totalAssetsDepositedOrReserved;
        if (assets == type(uint).max) {
            assets = _totalAssetsDepositedOrReserved - maxRelease();
        }

        totalAssetsDepositedOrReserved = _totalAssetsDepositedOrReserved - assets;
        underlying = IEVault(asset()).redeem(assets, receiver, address(this));
        _handleExcessCredit(_invariantCollateralAmount());

        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_RedeemUnderlying(assets, receiver);
    }

    ///
    // Twyne Custom Logic, for setting user target LTV, liquidation, rebalancing, teleporting, etc.
    ///

    /// @notice allow the user to set their own vault's LTV
    function setTwyneLiqLTV(uint _ltv) external onlyBorrowerAndNotExtLiquidated whenNotPaused nonReentrant {
        createVaultSnapshot();
        twyneVaultManager.checkLiqLTV(_ltv, targetVault, _asset);
        twyneLiqLTV = _ltv;
        _handleExcessCredit(_invariantCollateralAmount());
        evc.requireAccountAndVaultStatusCheck(address(this));
        emit T_SetTwyneLiqLTV(_ltv);
    }

    /// @notice check if a vault can be liquidated
    /// @dev calling canLiquidate() in other functions directly causes a read-only reentrancy issue,
    /// so move the logic to an internal function.
    function canLiquidate() external view nonReentrantView returns (bool) {
        return _canLiquidate();
    }

    // _canLiquidate() requires custom implementation per protocol integration
    function _canLiquidate() internal view virtual returns (bool);

    /// @notice Begin the liquidation process for this vault.
    /// @dev Liquidation needs to be done in a batch.
    /// If the vault is liquidatable, this fn makes liquidator the new borrower.
    /// Liquidator is then responsible to make this position healthy.
    /// Liquidator may choose to wind down the position and take collateral as profit.
    function liquidate() external callThroughEVC nonReentrant {
        createVaultSnapshot();
        require(totalAssetsDepositedOrReserved <= IERC20(asset()).balanceOf(address(this)), ExternallyLiquidated());
        address liquidator = _msgSender();
        require(liquidator != borrower, SelfLiquidation());
        require(_canLiquidate(), HealthyNotLiquidatable());

        // liquidator takes over this vault from the current borrower
        borrower = liquidator;

        collateralVaultFactory.setCollateralVaultLiquidated(liquidator);

        evc.requireAccountAndVaultStatusCheck(address(this));
    }

    /// @notice Checks whether the vault has undergone external liquidation
    /// @dev External liquidation occurs when the external lending protocol (e.g., Euler) directly liquidates
    /// collateral from this vault
    /// @dev Detects liquidation by comparing the tracked totalAssetsDepositedOrReserved with the actual
    /// token balance - if actual balance is less, external liquidation has occurred
    /// @return bool True if the vault has been externally liquidated, false otherwise
    function isExternallyLiquidated() external view nonReentrantView returns (bool) {
        return totalAssetsDepositedOrReserved > IERC20(asset()).balanceOf(address(this));
    }

    /// @notice Handles the aftermath of an external liquidation by the underlying lending protocol
    /// @dev Called when the vault's collateral was liquidated by the external protocol (e.g., Euler)
    /// @dev Steps performed:
    /// 1. Calculate and distribute the remaining collateral in this vault among
    ///  the liquidator, borrower and intermediate vault
    /// 2. Repay remaining external debt using funds from the liquidator
    /// 3. Reset the vault state so that it cannot be used again
    /// @dev Can only be called when the vault is actually in an externally liquidated state
    /// @dev Caller needs to call intermediateVault.liquidate(collateral_vault_address, collateral_vault_address, 0, 0)
    /// in the same EVC batch if there is any bad debt left at the end of this call
    /// @dev Implementation varies depending on the external protocol integration
    function handleExternalLiquidation() external virtual;

    /// @notice Calculates the amount of excess credit that can be released back to the intermediate vault
    /// @dev Excess credit exists when the relationship between borrower collateral and reserved credit becomes unbalanced
    /// @dev The calculation varies depending on the external protocol integration
    /// @return uint The amount of excess credit that can be released, denominated in the collateral asset
    function canRebalance() external view nonReentrantView returns (uint) {
        uint __invariantCollateralAmount = _invariantCollateralAmount();
        require(totalAssetsDepositedOrReserved > __invariantCollateralAmount, CannotRebalance());
        unchecked { return totalAssetsDepositedOrReserved - __invariantCollateralAmount; }
    }

    function _handleExcessCredit(uint __invariantCollateralAmount) internal virtual;

    function _invariantCollateralAmount() internal view virtual returns (uint);

    /// @notice Releases excess credit back to the intermediate vault
    /// @dev Excess credit exists when: liqLTV_twyne * C < safety_buffer * liqLTV_external * (C + C_LP)
    /// @dev Anyone can call this function to rebalance a position
    function rebalance() external callThroughEVC nonReentrant {
        uint __invariantCollateralAmount = _invariantCollateralAmount();
        require(totalAssetsDepositedOrReserved > __invariantCollateralAmount, CannotRebalance());
        require(totalAssetsDepositedOrReserved <= IERC20(asset()).balanceOf(address(this)), ExternallyLiquidated());
        _handleExcessCredit(__invariantCollateralAmount);
        emit T_Rebalance();
    }
}
"
    },
    "src/interfaces/IErrors.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.28;

interface IErrors {
    error AssetMismatch(); // 0x83c1010a
    error BadDebtNotSettled(); // 0x0c00c962
    error CallerNotOwnerOrCollateralVaultFactory(); // 0xda57fd09
    error CannotRebalance(); // 0x302a624a
    error ExternallyLiquidated(); // 0x111c379d
    error ExternalPositionUnhealthy(); // 0xf1cd786c
    error HealthyNotLiquidatable(); // 0x8e9797c5
    error IncorrectIndex(); // 0x07cc4d8f
    error IntermediateVaultAlreadySet(); // 0x00e658da
    error IntermediateVaultNotSet(); // 0x83cc6e74
    error NotCollateralVault(); // 0x4b344c2d
    error NotExternallyLiquidated(); // 0xdbd904f5
    error NotIntermediateVault(); // 0x8a6a3c99
    error NoLiquidationForZeroReserve(); // 0x818e1580
    error OnlyWETH(); // 0x01f180c9
    error ReceiverNotBorrower(); // 0x4a1d1a97
    error ReceiverNotCollateralVault(); // 0x9b6e6f6b
    error Reentrancy(); // 0xab143c06
    error RepayingMoreThanMax(); // 0x4fc5c4ba
    error SelfLiquidation(); // 0x44511af1
    error SnapshotNotTaken(); // 0xcb85efac
    error T_CV_OperationDisabled(); // 0x335c5fec
    error T_OperationDisabled(); // 0x4f6309cb
    error ValueOutOfRange(); // 0x4eb4f9fb
    error VaultStatusLiquidatable(); // 0x9c166fd0
    error ViolatorNotCollateralVault(); // 0x6af6f6bb

    // LeverageOperator errors
    error T_CallerNotBorrower();
    error T_CallerNotMorpho();
    error T_InvalidCollateralVault();
}"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Pausable
    struct PausableStorage {
        bool _paused;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;

    function _getPausableStorage() private pure returns (PausableStorage storage $) {
        assembly {
            $.slot := PausableStorageLocation
        }
    }

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal onlyInitializing {
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        PausableStorage storage $ = _getPausableStorage();
        return $._paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
        emit Unpaused(_msgSender());
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.22;

import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
     * See {_onlyProxy}.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
     *
     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
     * is expected to be the implementation slot in ERC-1967.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        } catch {
            // The implementation is not UUPS
            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/beacon/BeaconProxy.sol)

pragma solidity ^0.8.22;

import {IBeacon} from "./IBeacon.sol";
import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";

/**
 * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
 *
 * The beacon address can only be set once during construction, and cannot be changed afterwards. It is stored in an
 * immutable variable to avoid unnecessary storage reads, and also in the beacon storage slot specified by
 * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] so that it can be accessed externally.
 *
 * CAUTION: Since the beacon address can never be changed, you must ensure that you either control the beacon, or trust
 * the beacon to not upgrade the implementation maliciously.
 *
 * IMPORTANT: Do not use the implementation logic to modify the beacon storage slot. Doing so would leave the proxy in
 * an inconsistent state where the beacon storage slot does not match the beacon address.
 */
contract BeaconProxy is Proxy {
    // An immutable address for the beacon to avoid unnecessary SLOADs before each delegate call.
    address private immutable _beacon;

    /**
     * @dev Initializes the proxy with `beacon`.
     *
     * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
     * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity
     * constructor.
     *
     * Requirements:
     *
     * - `beacon` must be a contract with the interface {IBeacon}.
     * - If `data` is empty, `msg.value` must be zero.
     */
    constructor(address beacon, bytes memory data) payable {
        ERC1967Utils.upgradeBeaconToAndCall(beacon, data);
        _beacon = beacon;
    }

    /**
     * @dev Returns the current implementation address of the associated beacon.
     */
    function _implementation() internal view virtual override returns (address) {
        return IBeacon(_getBeacon()).implementation();
    }

    /**
     * @dev Returns the beacon.
     */
    function _getBeacon() internal view virtual returns (address) {
        return _beacon;
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

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

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "src/twyne/VaultManager.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.28;

import {UUPSUpgradeable} from "openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {EulerRouter} from "euler-price-oracle/src/EulerRouter.sol";
import {IEVault} from "euler-vault-kit/EVault/IEVault.sol";
import {IErrors} from "src/interfaces/IErrors.sol";
import {IEvents} from "src/interfaces/IEvents.sol";
import {RevertBytes} from "euler-vault-kit/EVault/shared/lib/RevertBytes.sol";
import {CollateralVaultBase} from "src/twyne/CollateralVaultBase.sol";
import {CollateralVaultFactory} from "src/TwyneFactory/CollateralVaultFactory.sol";

/// @title IRMLinearKink
/// @notice To contact the team regarding security matters, visit https://twyne.xyz/security
/// @notice Manages twyne parameters that affect it globally: assets allowed, LTVs, interest rates.
/// To be owned by Twyne multisig.
contract VaultManager is UUPSUpgradeable, OwnableUpgradeable, IErrors, IEvents {
    uint internal constant MAXFACTOR = 1e4;

    address public collateralVaultFactory;

    mapping(address collateralAddress => uint16 maxTwyneLiqLTV) public maxTwyneLTVs; // mapped to underlying asset in the collateral vault (can use intermediateVault for now)
    mapping(address collateralAddress => uint16 externalLiqBuffer) public externalLiqBuffers; // mapped to underlying asset in the collateral vault (can use intermediateVault for now)

    EulerRouter public oracleRouter;

    mapping(address collateralAddress => address intermediateVault) internal intermediateVaults;
    mapping(address intermediateVault => mapping(address targetVault => bool allowed)) public isAllowedTargetVault;

    mapping(address intermediateVault => address[] targetVaults) public allowedTargetVaultList;

    uint[50] private __gap;

    modifier onlyCollateralVaultFactoryOrOwner() {
        require(msg.sender == owner() || msg.sender == collateralVaultFactory, CallerNotOwnerOrCollateralVaultFactory());
        _;
    }

    constructor() {
        _disableInitializers();
    }

    /// @param _owner address of initial owner
    /// @param _factory address of collateral vault factory deployment
    function initialize(address _owner, address _factory) external initializer {
        __Ownable_init(_owner);
        __UUPSUpgradeable_init();
        collateralVaultFactory = _factory;
    }

    /// @dev override required by UUPSUpgradeable
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /// @dev increment the version for proxy upgrades
    function version() external pure returns (uint) {
        return 1;
    }

    /// @notice Set oracleRouter address. Governance-only.
    function setOracleRouter(address _oracle) external onlyOwner {
        oracleRouter = EulerRouter(_oracle);
        emit T_SetOracleRouter(_oracle);
    }

    /// @notice Get a collateral vault's intermediate vault.
    /// @param _collateralAddress the collateral asset held by the intermediate vault.
    /// @return vault intermediate vault for the given _collateralAddress.
    function getIntermediateVault(address _collateralAddress) external view returns (address vault) {
        vault = intermediateVaults[_collateralAddress];
        require(vault != address(0), IntermediateVaultNotSet());
    }


    /// @notice Set a collateral vault's intermediate vault. Governance-only.
    /// @param _intermediateVault address of the intermediate vault.
    function setIntermediateVault(IEVault _intermediateVault) external onlyOwner {
        address creditAsset = _intermediateVault.asset();
        require(intermediateVaults[creditAsset] == address(0), IntermediateVaultAlreadySet());
        intermediateVaults[creditAsset] = address(_intermediateVault);
        emit T_SetIntermediateVault(address(_intermediateVault));
    }

    /// @notice Set an allowed target vault for a specific intermediate vault. Governance-only.
    /// @param _intermediateVault address of the intermediate vault.
    /// @param _targetVault The target vault that should be allowed for the intermediate vault.
    function setAllowedTargetVault(address _intermediateVault, address _targetVault) external onlyOwner {
        require(IEVault(_intermediateVault).unitOfAccount() == IEVault(_targetVault).unitOfAccount(), AssetMismatch());
        isAllowedTargetVault[_intermediateVault][_targetVault] = true;
        allowedTargetVaultList[_intermediateVault].push(_targetVault);
        emit T_AddAllowedTargetVault(_intermediateVault, _targetVault);
    }

    /// @notice Remove an allowed target vault for a specific intermediate vault. Governance-only.
    /// @param _intermediateVault address of the intermediate vault.
    /// @param _targetVault The target vault that should be allowed for the intermediate vault.
    /// @param _index The index at which this _targetVault is stored in `allowedTargetVaultList`.
    function removeAllowedTargetVault(address _intermediateVault, address _targetVault, uint _index) external onlyOwner {
        isAllowedTargetVault[_intermediateVault][_targetVault] = false;

        require(allowedTargetVaultList[_intermediateVault][_index] == _targetVault, IncorrectIndex());

        uint lastIndex = allowedTargetVaultList[_intermediateVault].length - 1;
        if (_index != lastIndex) allowedTargetVaultList[_intermediateVault][_index] = allowedTargetVaultList[_intermediateVault][lastIndex];
        allowedTargetVaultList[_intermediateVault].pop();
        emit T_RemoveAllowedTargetVault(_intermediateVault, _targetVault, _index);
    }

    /// @notice Return the length of allowedTargetVaultList. Useful for frontend.
    function targetVaultLength(address _intermediateVault) external view returns (uint) {
        return allowedTargetVaultList[_intermediateVault].length;
    }

    /// @notice Set protocol-wide maxTwyneLiqLTV. Governance-only.
    /// @param _ltv new maxTwyneLiqLTV value.
    function setMaxLiquidationLTV(address _collateralAddress, uint16 _ltv) external onlyOwner {
        require(_ltv <= MAXFACTOR, ValueOutOfRange());
        maxTwyneLTVs[_collateralAddress] = _ltv;
        emit T_SetMaxLiqLTV(_collateralAddress, _ltv);
    }

    /// @notice Set protocol-wide externalLiqBuffer. Governance-only.
    /// @param _liqBuffer new externalLiqBuffer value.
    function setExternalLiqBuffer(address _collateralAddress, uint16 _liqBuffer) external onlyOwner {
        require(0 != _liqBuffer  && _liqBuffer <= MAXFACTOR, ValueOutOfRange());
        externalLiqBuffers[_collateralAddress] = _liqBuffer;
        emit T_SetExternalLiqBuffer(_collateralAddress, _liqBuffer);
    }

    /// @notice Set new collateralVaultFactory address. Governance-only.
    /// @param _factory new collateralVaultFactory address.
    function setCollateralVaultFactory(address _factory) external onlyOwner {
        collateralVaultFactory = _factory;
        emit T_SetCollateralVaultFactory(_factory);
    }

    /// @notice Set new LTV values for an intermediate vault by calling EVK.setLTV(). Callable by governance or collateral vault factory.
    /// @param _intermediateVault address of the intermediate vault.
    /// @param _collateralVault address of the collateral vault.
    /// @param _borrowLimit new borrow LTV.
    /// @param _liquidationLimit new liquidation LTV.
    /// @param _rampDuration ramp duration in seconds (0 for immediate effect) during which the liquidation LTV will change.
    function setLTV(IEVault _intermediateVault, address _collateralVault, uint16 _borrowLimit, uint16 _liquidationLimit, uint32 _rampDuration)
        external
        onlyCollateralVaultFactoryOrOwner
    {
        require(CollateralVaultBase(_collateralVault).asset() == _intermediateVault.asset(), AssetMismatch());
        _intermediateVault.setLTV(_collateralVault, _borrowLimit, _liquidationLimit, _rampDuration);
        emit T_SetLTV(address(_intermediateVault), _collateralVault, _borrowLimit, _liquidationLimit, _rampDuration);
    }

    /// @notice Set new oracleRouter resolved vault value. Callable by governance or collateral vault factory.
    /// @param _vault EVK or collateral vault address. Must implement `convertToAssets()`.
    /// @param _allow bool value to pass to govSetResolvedVault. True to configure the vault, false to clear the record.
    /// @dev called by createCollateralVault() when a new collateral vault is created so collateral can be price properly.
    /// @dev Configures the collateral vault to use internal pricing via `convertToAssets()`.
    function setOracleResolvedVault(address _vault, bool _allow) external onlyCollateralVaultFactoryOrOwner {
        oracleRouter.govSetResolvedVault(_vault, _allow);
        emit T_SetOracleResolvedVault(_vault, _allow);
    }

    /// @notice Perform an arbitrary external call. Governance-only.
    /// @dev VaultManager is an owner/admin of many contracts in the Twyne system.
    /// @dev This function helps Governance in case a specific a specific external function call was not implemented.
    function doCall(address to, uint value, bytes memory data) external payable onlyOwner {
        (bool success, bytes memory _data) = to.call{value: value}(data);
        if (!success) RevertBytes.revertBytes(_data);
        emit T_DoCall(to, value, data);
    }

    /// @notice Checks that the user-set LTV is within the min and max bounds.
    /// @param _liqLTV The LTV that the user wants to use for their collateral vault.
    /// @param _targetVault The target vault used for the borrow by collateral vault.
    /// @param _collateralAddress The collateral asset used by collateral vault.
    function checkLiqLTV(uint _liqLTV, address _targetVault, address _collateralAddress) external view {
        uint16 minLTV = IEVault(_targetVault).LTVLiquidation(_collateralAddress);
        require(uint(minLTV) * uint(externalLiqBuffers[_collateralAddress]) <= _liqLTV * MAXFACTOR && _liqLTV <= maxTwyneLTVs[_collateralAddress], ValueOutOfRange());
    }

    receive() external payable {}
}
"
    },
    "lib/euler-vault-kit/src/EVault/IEVault.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.8.0;

import {IVault as IEVCVault} from "ethereum-vault-connector/interfaces/IVault.sol";

// Full interface of EVault and all it's modules

/// @title IInitialize
/// @notice Interface of the initialization module of EVault
interface IInitialize {
    /// @notice Initialization of the newly deployed proxy contract
    /// @param proxyCreator Account which created the proxy or should be the initial governor
    function initialize(address proxyCreator) external;
}

/// @title IERC20
/// @notice Interface of the EVault's Initialize module
interface IERC20 {
    /// @notice Vault share token (eToken) name, ie "Euler Vault: DAI"
    /// @return The name of the eToken
    function name() external view returns (string memory);

    /// @notice Vault share token (eToken) symbol, ie "eDAI"
    /// @return The symbol of the eToken
    function symbol() external view returns (string memory);

    /// @notice Decimals, the same as the asset's or 18 if the asset doesn't implement `decimals()`
    /// @return The decimals of the eToken
    function decimals() external view returns (uint8);

    /// @notice Sum of all eToken balances
    /// @return The total supply of the eToken
    function totalSupply() external view returns (uint256);

    /// @notice Balance of a particular account, in eTokens
    /// @param account Address to query
    /// @return The balance of the account
    function balanceOf(address account) external view returns (uint256);

    /// @notice Retrieve the current allowance
    /// @param holder The account holding the eTokens
    /// @param spender Trusted address
    /// @return The allowance from holder for spender
    function allowance(address holder, address spender) external view returns (uint256);

    /// @notice Transfer eTokens to another address
    /// @param to Recipient account
    /// @param amount In shares.
    /// @return True if transfer succeeded
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Transfer eTokens from one address to another
    /// @param from This address must've approved the to address
    /// @param to Recipient account
    /// @param amount In shares
    /// @return True if transfer succeeded
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice Allow spender to access an amount of your eTokens
    /// @param spender Trusted address
    /// @param amount Use max uint for "infinite" allowance
    /// @return True if approval succeeded
    function approve(address spender, uint256 amount) external returns (bool);
}

/// @title IToken
/// @notice Interface of the EVault's Token module
interface IToken is IERC20 {
    /// @notice Transfer the full eToken balance of an address to another
    /// @param from This address must've approved the to address
    /// @param to Recipient account
    /// @return True if transfer succeeded
    function transferFromMax(address from, address to) external returns (bool);
}

/// @title IERC4626
/// @notice Interface of an ERC4626 vault
interface IERC4626 {
    /// @notice Vault's underlying asset
    /// @return The vault's underlying asset
    function asset() external view returns (address);

    /// @notice Total amount of managed assets, cash and borrows
    /// @return The total amount of assets
    function totalAssets() external view returns (uint256);

    /// @notice Calculate amount of assets corresponding to the requested 

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Pausable, Swap, Liquidity, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x4618659e71155a9eeb3d1ba3166fac63f7dd405a|verified:true|block:23378667|tx:0xaf019bc19d9be25d94dce40a61b14720f5c232f391981aafc961c9785ee84a8f|first_check:1758105013

Submitted on: 2025-09-17 12:30:15

Comments

Log in to comment.

No comments yet.