Assessment reports>Multisafe USPC Contracts>Medium findings>Missing price staleness check in ,IUSPCHub.vaultSubscription, and ,IUSPCHub.vaultRedemption
Category: Business Logic

Missing price staleness check in IUSPCHub.vaultSubscription and IUSPCHub.vaultRedemption

Medium Impact
Medium Severity
Medium Likelihood

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:

  1. 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.

  2. As time passes, the timestamp of the latest price exceeds MAX_PRICE_AGE (24 hours).

  3. A regular user calls requestSubscription and waits for the administrator to process it. When the administrator calls processSubscriptions, the subscription is incorrectly rejected by PriceTooOld because the price has expired, preventing the user from completing the subscription.

  4. Simultaneously, a Vault holding VAULT_ROLE and on the whitelist calls vaultSubscription(amount), which successfully mints iUSPC tokens using the expired price.

  5. The Vault can then call vaultRedemption to 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.

Zellic © 2025Back to top ↑