Description
The lending pool relies on price data obtained from an external oracle implementing the SEP-40 interface. However, there is a critical inconsistency in how price freshness is enforced between the oracle and the pool.
The oracle contract (RWAOracle) provides two relevant methods:
- lastprice() → returns the most recent price without checking staleness
- lastprice_if_fresh() → returns price only if within max_staleness threshold
While the oracle correctly enforces staleness through lastprice_if_fresh, the pool integration (in Oracles module) does not use this protection. Instead, it directly calls:
oracle_client.lastprice(...)
and applies its own validation:
if oracle_price_data.timestamp + 7 * 24 * 60 * 60 < current_time {
return Err(Error::InvalidOraclePrice);
}
This creates a mismatch in staleness assumptions:
- Oracle-defined staleness (configurable via max_staleness, e.g., 5 minutes for volatile assets)
- Pool-defined staleness (hardcoded to 7 days)
Root Cause
The vulnerability arises because:
- The oracle explicitly marks prices as stale using:
- The pool bypasses this mechanism entirely and instead:
- fetches raw price via lastprice()
- applies a much weaker, hardcoded staleness check (7 days)
As a result:
Prices that are considered invalid (stale) by the oracle are still considered valid by the pool.
Inconsistent Trust Model
This leads to a broken trust boundary:
- The oracle is designed to be the source of truth for price validity
- The pool overrides this logic with its own weaker rules
This violates the intended design assumption that:
"Consumers of the oracle should respect its freshness guarantees"
Impact on Protocol Logic
The stale price is then used in core financial calculations:
- Collateral valuation
- Borrow limits
- Health factor (HF)
- Liquidation eligibility
Because the pool accepts stale prices:
- Collateral values can be overestimated
- Health factors can be artificially inflated
Critical Outcome
This results in the following condition:
Condition == Value
Real market price == Low (position unsafe)
Oracle freshness == Stale (invalid)
Pool price used == Old (high)
Calculated HF == ≥ 1.0 (healthy)
Actual HF == < 1.0 (liquidatable)
Security Consequence
Positions that should be liquidated become unliquidatable because:
if health_factor >= 1.0 {
revert(); // liquidation blocked
}
This leads to:
- Liquidation failure
- Accumulation of bad debt
- Protocol insolvency risk
Summary
The vulnerability is caused by:
- Ignoring oracle-level freshness guarantees
- Replacing them with a weaker, hardcoded validation
- Using stale data in critical financial logic
This creates a systemic pricing inconsistency that directly impacts liquidation correctness and protocol solvency.
Impact (HIGH Severity Justification + Economic Exploit)
Severity: HIGH
This vulnerability directly affects the core solvency and liquidation mechanism of the protocol.
Why HIGH Severity?
- ❌ Liquidation Failure
The pool calculates the health factor (HF) using stale prices.
As a result:
- Positions that are actually undercollateralized (HF < 1.0)
- Are considered healthy (HF ≥ 1.0) by the protocol
if health_factor >= 1.0 {
revert(); // liquidation blocked
}
Liquidators are unable to execute liquidation.
2.Collateral Mispricing
Because stale prices are used:
- Collateral is overvalued
- Borrowers appear sufficiently collateralized when they are not
3.Bad Debt Accumulation
Since liquidation is blocked:
- Positions remain open
- Collateral value continues to drop (real market)
- Debt cannot be repaid via liquidation
Eventually:
collateral < debt → bad debt
4.Protocol Insolvency Risk
If multiple positions are affected:
- The protocol accumulates under-collateralized loans
- Losses are absorbed by:
- Liquidity providers (LPs)
- Protocol treasury
Description
The lending pool relies on price data obtained from an external oracle implementing the SEP-40 interface. However, there is a critical inconsistency in how price freshness is enforced between the oracle and the pool.
The oracle contract (RWAOracle) provides two relevant methods:
While the oracle correctly enforces staleness through lastprice_if_fresh, the pool integration (in Oracles module) does not use this protection. Instead, it directly calls:
and applies its own validation:
This creates a mismatch in staleness assumptions:
Root Cause
The vulnerability arises because:
As a result:
Prices that are considered invalid (stale) by the oracle are still considered valid by the pool.
Inconsistent Trust Model
This leads to a broken trust boundary:
This violates the intended design assumption that:
"Consumers of the oracle should respect its freshness guarantees"
Impact on Protocol Logic
The stale price is then used in core financial calculations:
Because the pool accepts stale prices:
Critical Outcome
This results in the following condition:
Condition == Value
Real market price == Low (position unsafe)
Oracle freshness == Stale (invalid)
Pool price used == Old (high)
Calculated HF == ≥ 1.0 (healthy)
Actual HF == < 1.0 (liquidatable)
Security Consequence
Positions that should be liquidated become unliquidatable because:
This leads to:
Summary
The vulnerability is caused by:
This creates a systemic pricing inconsistency that directly impacts liquidation correctness and protocol solvency.
Impact (HIGH Severity Justification + Economic Exploit)
Severity: HIGH
This vulnerability directly affects the core solvency and liquidation mechanism of the protocol.
Why HIGH Severity?
The pool calculates the health factor (HF) using stale prices.
As a result:
Liquidators are unable to execute liquidation.
2.Collateral Mispricing
Because stale prices are used:
3.Bad Debt Accumulation
Since liquidation is blocked:
Eventually:
4.Protocol Insolvency Risk
If multiple positions are affected:
- Liquidity providers (LPs)
- Protocol treasury