-
Notifications
You must be signed in to change notification settings - Fork 0
Creamy Indigo Dachshund - realizeRestakerInterest() mints unbacked debt leading to protocol insolvency #667
Description
Creamy Indigo Dachshund
High
realizeRestakerInterest() mints unbacked debt leading to protocol insolvency
Summary
The realizeRestakerInterest() function updates internal debt and borrow state based on accrued restaking rewards. However, this function fails to verify whether there is actual yield available in the vault to support the update. As a result, it can mint synthetic protocol debt backed by nothing — leading to systemic insolvency and diluted capital. The flaw affects the core reserve mechanics and can be leveraged to inflate liabilities and compromise solvency across the system.
Root Cause
Debt is realized without confirming asset availability.
The protocol records unrealized restaker interest over time. When a user triggers realizeRestakerInterest(), the function finalizes the interest by increasing:
VaultStorage.totalBorrowsreservesData[_asset].debt
But the code does not check that there are actually enough funds in the vault to honor this "realized" interest:
$.totalBorrows += realization;
$.reservesData[_asset].debt += realization;This can mint phantom debt that does not correspond to any yield — fundamentally breaking the 1:1 asset-debt assumption and putting the protocol underwater.
Internal Pre-conditions
- A restaker deposits a large sum to accumulate
unrealizedRestakerInterest - The protocol's yield for that asset is delayed or not yet available
- Vault
availableBalance(_asset)is insufficient to support the realization
External Pre-conditions
- A restaker deposits a large sum to accumulate
unrealizedRestakerInterest - The protocol's yield for that asset is delayed or not yet available
- Vault
availableBalance(_asset)is insufficient to support the realization
Attack Path
Exploitation Loop:
- Attacker stakes a large amount of capital
- Waits until
unrealizedRestakerInterestaccrues - Calls
realizeRestakerInterest() - Internal state now treats this amount as owed to the restaker
- No check is performed against actual vault liquidity
- Protocol reports inflated borrow levels and reserve debt
- Liquidation math and pricing logic are now incorrect
- Protocol becomes underwater without realizing it
Impact
Protocol-Wide Insolvency Risk
- Minted Debt Without Yield: Dilutes the backing of capToken and reserve assets
- Incorrect Borrowing Math: Liquidators and interest rate logic use
totalBorrows, which is now corrupted - Silent Insolvency: Protocol continues operating with inflated liabilities and incorrect collateral ratios
Accounting Contamination
availableBalance()becomes misleading- Borrower health factors are skewed
- Interest rate curve adjusts to wrong utilization numbers
Liquidation Risk
- Underlying collateral appears worse than it is (bad for borrowers)
- Liquidation math triggers too early or not at all (bad for protocol)
PoC
function testPhantomDebtViaRealizeInterest() public {
// Step 1: Restaker deposits into protocol
stake(10_000_000e18); // 10M asset
// Step 2: Wait for interest to accrue
warp(block.timestamp + 7 days);
// Step 3: Realize interest
uint256 preVaultBalance = vaultBalance();
uint256 preDebt = totalDebt();
realizeRestakerInterest(asset); // Does not check if vault has enough
// Step 4: Assert phantom debt created
assert(totalDebt() > preDebt);
assert(vaultBalance() == preVaultBalance); // No yield actually added
// Optional: Simulate redemption or borrow to drain funds
}Mitigation
Insert Debt realization backing check before any update to totalBorrows or reserve.debt