Initial commit
This commit is contained in:
318
contracts/core/DBISInstitutionalVault.sol
Normal file
318
contracts/core/DBISInstitutionalVault.sol
Normal file
@@ -0,0 +1,318 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user