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
Submitted on: 2025-09-17 12:30:15
Comments
Log in to comment.
No comments yet.