Missing price staleness check in IUSPCHub.vaultSubscription and IUSPCHub.vaultRedemption
Description
The IUSPCHub.vaultSubscription and IUSPCHub.vaultRedemption functions provide instant processing capabilities for whitelisted Vaults, allowing users to directly retrieve prices using pricer.getLatestPrice():
// Get current price for immediate processing
uint256 currentPrice = $.pricer.getLatestPrice();
// [...]
/**
* @notice Gets the latest price of the asset
* @return The latest price with 36 decimal precision
*/
function getLatestPrice() external view override whenNotPaused returns (uint256) {
PricerStorage storage $ = _getPricerStorage();
require($.latestPriceId != 0, NoPricesSet());
return $.prices[$.latestPriceId].price;
}The function calls pricer.getLatestPrice() to vaultSubscription and vaultRedemption only return the price value, not the timestamp. The code does not call pricer.getLatestPriceInfo() to retrieve the timestamp, nor does it perform the check that block.timestamp - timestamp <= MAX_PRICE_AGE.
Price staleness checks are correctly implemented in both the standard processes IUSPCHub.processSubscriptions and IUSPCHub.processRedemptions:
require(block.timestamp - timestamps[i] <= MAX_PRICE_AGE, PriceTooOld());Impact
Consider the following scenario:
The price admin calls
Pricer.addCurrentPrice(price)to update the NAV price, but due to various reasons (system failure, human error, etc.), the price is not updated for an extended period.As time passes, the timestamp of the latest price exceeds
MAX_PRICE_AGE(24 hours).A regular user calls
requestSubscriptionand waits for the administrator to process it. When the administrator callsprocessSubscriptions, the subscription is incorrectly rejected byPriceTooOldbecause the price has expired, preventing the user from completing the subscription.Simultaneously, a Vault holding
VAULT_ROLEand on the whitelist callsvaultSubscription(amount), which successfully mints iUSPC tokens using the expired price.The Vault can then call
vaultRedemptionto redeem USDC using the same expired price, completing arbitrage.
Recommendations
Consider adding require(block.timestamp - timestamps[i] <= MAX_PRICE_AGE, PriceTooOld()); in IUSPCHub.vaultSubscription and IUSPCHub.vaultRedemption.
Remediation
This issue has been acknowledged by Coinshift, and a fix was implemented in commit 3e3b0fde↗.