// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../interfaces/IVault.sol"; import "../interfaces/IOracleAdapter.sol"; /** * @title DBISInstitutionalVault * @notice Institutional vault representing a leveraged DeFi position * @dev Tracks collateral and debt across multiple assets, enforces invariants */ contract DBISInstitutionalVault is IVault, Ownable, AccessControl, ReentrancyGuard { bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); bytes32 public constant KERNEL_ROLE = keccak256("KERNEL_ROLE"); IOracleAdapter public oracleAdapter; // Aave v3 Pool interface (simplified) interface IPoolV3 { function getUserAccountData(address user) external view returns ( uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); } IPoolV3 public aavePool; address public aaveUserAccount; // Address of the Aave position // Internal position tracking (for non-Aave assets) struct AssetPosition { uint256 collateral; // Amount deposited as collateral uint256 debt; // Amount borrowed } mapping(address => AssetPosition) private assetPositions; address[] private trackedAssets; // Constants uint256 private constant HF_SCALE = 1e18; uint256 private constant PRICE_SCALE = 1e8; constructor( address _oracleAdapter, address _aavePool, address _aaveUserAccount, address initialOwner ) Ownable(initialOwner) { require(_oracleAdapter != address(0), "Invalid oracle"); require(_aavePool != address(0), "Invalid Aave pool"); require(_aaveUserAccount != address(0), "Invalid Aave account"); oracleAdapter = IOracleAdapter(_oracleAdapter); aavePool = IPoolV3(_aavePool); aaveUserAccount = _aaveUserAccount; _grantRole(DEFAULT_ADMIN_ROLE, initialOwner); } /** * @notice Get total collateral value in USD (scaled by 1e8) */ function getTotalCollateralValue() public view override returns (uint256) { uint256 total = 0; // Get Aave collateral try aavePool.getUserAccountData(aaveUserAccount) returns ( uint256 totalCollateralBase, uint256, uint256, uint256, uint256, uint256 ) { // Aave returns in base currency (USD, scaled by 1e8) total += totalCollateralBase; } catch {} // Add non-Aave collateral for (uint256 i = 0; i < trackedAssets.length; i++) { address asset = trackedAssets[i]; AssetPosition storage pos = assetPositions[asset]; if (pos.collateral > 0) { try oracleAdapter.convertAmount(asset, pos.collateral, address(0)) returns (uint256 value) { total += value; } catch {} } } return total; } /** * @notice Get total debt value in USD (scaled by 1e8) */ function getTotalDebtValue() public view override returns (uint256) { uint256 total = 0; // Get Aave debt try aavePool.getUserAccountData(aaveUserAccount) returns ( uint256, uint256 totalDebtBase, uint256, uint256, uint256, uint256 ) { // Aave returns in base currency (USD, scaled by 1e8) total += totalDebtBase; } catch {} // Add non-Aave debt for (uint256 i = 0; i < trackedAssets.length; i++) { address asset = trackedAssets[i]; AssetPosition storage pos = assetPositions[asset]; if (pos.debt > 0) { try oracleAdapter.convertAmount(asset, pos.debt, address(0)) returns (uint256 value) { total += value; } catch {} } } return total; } /** * @notice Get current health factor (scaled by 1e18) */ function getHealthFactor() public view override returns (uint256) { // Try to get from Aave first (most accurate) try aavePool.getUserAccountData(aaveUserAccount) returns ( uint256, uint256, uint256, uint256, uint256, uint256 healthFactor ) { return healthFactor; } catch {} // Fallback: calculate manually uint256 collateralValue = getTotalCollateralValue(); uint256 debtValue = getTotalDebtValue(); if (debtValue == 0) { return type(uint256).max; // Infinite health factor if no debt } // Health Factor = (Collateral * Liquidation Threshold) / Debt // Simplified: use 80% LTV as threshold return (collateralValue * 80e18 / 100) / debtValue; } /** * @notice Get current LTV (Loan-to-Value ratio, scaled by 1e18) */ function getLTV() public view override returns (uint256) { uint256 collateralValue = getTotalCollateralValue(); if (collateralValue == 0) { return 0; } uint256 debtValue = getTotalDebtValue(); return (debtValue * HF_SCALE) / collateralValue; } /** * @notice Record addition of collateral */ function recordCollateralAdded(address asset, uint256 amount) external override onlyRole(KERNEL_ROLE) { require(asset != address(0), "Invalid asset"); require(amount > 0, "Invalid amount"); if (assetPositions[asset].collateral == 0 && assetPositions[asset].debt == 0) { trackedAssets.push(asset); } assetPositions[asset].collateral += amount; emit CollateralAdded(asset, amount); } /** * @notice Record repayment of debt */ function recordDebtRepaid(address asset, uint256 amount) external override onlyRole(KERNEL_ROLE) { require(asset != address(0), "Invalid asset"); require(amount > 0, "Invalid amount"); require(assetPositions[asset].debt >= amount, "Debt insufficient"); assetPositions[asset].debt -= amount; emit DebtRepaid(asset, amount); } /** * @notice Take a position snapshot for invariant checking */ function snapshotPosition() external override onlyRole(KERNEL_ROLE) returns ( uint256 collateralBefore, uint256 debtBefore, uint256 healthFactorBefore ) { collateralBefore = getTotalCollateralValue(); debtBefore = getTotalDebtValue(); healthFactorBefore = getHealthFactor(); } /** * @notice Verify position improved (invariant check) * @dev Enforces: Debt↓ OR Collateral↑ OR HF↑ */ function verifyPositionImproved( uint256 collateralBefore, uint256 debtBefore, uint256 healthFactorBefore ) external view override returns (bool success) { uint256 collateralAfter = getTotalCollateralValue(); uint256 debtAfter = getTotalDebtValue(); uint256 healthFactorAfter = getHealthFactor(); // Emit snapshot event (best effort) // Note: Events can't be emitted from view functions in Solidity // This would be done in the calling contract // Check invariants: // 1. Debt decreased OR // 2. Collateral increased OR // 3. Health factor improved bool debtDecreased = debtAfter < debtBefore; bool collateralIncreased = collateralAfter > collateralBefore; bool hfImproved = healthFactorAfter > healthFactorBefore; // All three must improve for strict amortization return debtDecreased && collateralIncreased && hfImproved; } /** * @notice Grant operator role */ function grantOperator(address operator) external onlyOwner { _grantRole(OPERATOR_ROLE, operator); } /** * @notice Grant kernel role */ function grantKernel(address kernel) external onlyOwner { _grantRole(KERNEL_ROLE, kernel); } /** * @notice Revoke operator role */ function revokeOperator(address operator) external onlyOwner { _revokeRole(OPERATOR_ROLE, operator); } /** * @notice Revoke kernel role */ function revokeKernel(address kernel) external onlyOwner { _revokeRole(KERNEL_ROLE, kernel); } /** * @notice Update oracle adapter */ function setOracleAdapter(address newOracle) external onlyOwner { require(newOracle != address(0), "Invalid oracle"); oracleAdapter = IOracleAdapter(newOracle); } /** * @notice Update Aave pool */ function setAavePool(address newPool) external onlyOwner { require(newPool != address(0), "Invalid pool"); aavePool = IPoolV3(newPool); } /** * @notice Update Aave user account */ function setAaveUserAccount(address newAccount) external onlyOwner { require(newAccount != address(0), "Invalid account"); aaveUserAccount = newAccount; } /** * @notice Get asset position */ function getAssetPosition(address asset) external view returns (uint256 collateral, uint256 debt) { AssetPosition storage pos = assetPositions[asset]; return (pos.collateral, pos.debt); } /** * @notice Get all tracked assets */ function getTrackedAssets() external view returns (address[] memory) { return trackedAssets; } }