HIGH severity: Incorrect oracle staleness period leads to price feed DoS. The PriceOracle contract uses a single staleness period for all price feeds, despite different assets having different update frequencies (e.g., stablecoins updated every 24 hours vs. ETH/BTC every hour). This misconfiguration causes the oracle to either reject valid prices from slow-updating assets...

Vulnerable code:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import { Access } from "../access/Access.sol";
import { IOracle } from "../interfaces/IOracle.sol";
import { IPriceOracle } from "../interfaces/IPriceOracle.sol";
import { PriceOracleStorageUtils } from "../storage/PriceOracleStorageUtils.sol";

/// @title Oracle for fetching prices
/// @author kexley, @capLabs
/// @dev Payloads are stored on this contract and calculation logic is hosted on external libraries
contract PriceOracle is IPriceOracle, Access, PriceOracleStorageUtils {
    /// @dev Initialize the price oracle
    /// @param _accessControl Access control address
    /// @param _staleness Staleness period in seconds for asset prices
    function __PriceOracle_init(address _accessControl, uint256 _staleness) internal onlyInitializing {
        __Access_init(_accessControl);
        __PriceOracle_init_unchained(_staleness);
    }

    /// @dev Initialize unchained
    /// @param _staleness Staleness period in seconds for asset prices
    function __PriceOracle_init_unchained(uint256 _staleness) internal onlyInitializing {
        getPriceOracleStorage().staleness = _staleness;
    }

    /// @notice Fetch the price for an asset
    /// @dev If initial price fetch fails or is stale then a backup source is used, reverts if both fail
    /// @param _asset Asset address
    /// @return price Price of the asset
    /// @return lastUpdated Latest timestamp of the price
    function getPrice(address _asset) external view returns (uint256 price, uint256 lastUpdated) {
        PriceOracleStorage storage $ = getPriceOracleStorage();
        IOracle.OracleData memory data = $.oracleData[_asset];

        (price, lastUpdated) = _getPrice(data.adapter, data.payload);

        if (price == 0 || _isStale(lastUpdated)) {
            data = $.backupOracleData[_asset];
            (price, lastUpdated) = _getPrice(data.adapter, data.payload);

            if (_isStale(lastUpdated)) revert StalePrice(lastUpdated);
        }
    }

    /// @notice View the oracle data for an asset
    /// @param _asset Asset address
    /// @return data Oracle data for an asset
    function priceOracleData(address _asset) external view returns (IOracle.OracleData memory data) {
        data = getPriceOracleStorage().oracleData[_asset];
    }

    /// @notice View the backup oracle data for an asset
    /// @param _asset Asset address
    /// @return data Backup oracle data for an asset
    function priceBackupOracleData(address _asset) external view returns (IOracle.OracleData memory data) {
        data = getPriceOracleStorage().backupOracleData[_asset];
    }

    /// @notice View the staleness period for asset prices
    /// @return stalenessPeriod Staleness period in seconds for asset prices
    function staleness() external view returns (uint256 stalenessPeriod) {
        stalenessPeriod = getPriceOracleStorage().staleness;
    }

    /// @notice Set a price source for an asset
    /// @param _asset Asset address
    /// @param _oracleData Oracle data
    function setPriceOracleData(address _asset, IOracle.OracleData calldata _oracleData)
        external
        checkAccess(this.setPriceOracleData.selector)
    {
        getPriceOracleStorage().oracleData[_asset] = _oracleData;
        emit SetPriceOracleData(_asset, _oracleData);
    }

    /// @notice Set a backup price source for an asset
    /// @param _asset Asset address
    /// @param _oracleData Oracle data
    function setPriceBackupOracleData(address _asset, IOracle.OracleData calldata _oracleData)
        external
        checkAccess(this.setPriceBackupOracleData.selector)
    {
        getPriceOracleStorage().backupOracleData[_asset] = _oracleData;
        emit SetPriceBackupOracleData(_asset, _oracleData);
    }

    /// @notice Set the staleness period for asset prices
    /// @param _staleness Staleness period in seconds for asset prices
    function setStaleness(uint256 _staleness) external checkAccess(this.setStaleness.selector) {
        getPriceOracleStorage().staleness = _staleness;
        emit SetStaleness(_staleness);
    }

    /// @dev Calculate price using an adapter and payload but do not revert on errors
    /// @param _adapter Adapter for calculation logic
    /// @param _payload Encoded call to adapter with all required data
    /// @return price Calculated price
    function _getPrice(address _adapter, bytes memory _payload)
        private
        view
        returns (uint256 price, uint256 lastUpdated)
    {
        (bool success, bytes memory returnedData) = _adapter.staticcall(_payload);
        if (success) (price, lastUpdated) = abi.decode(returnedData, (uint256, uint256));
    }

    /// @dev Check if a price is stale
    /// @param _lastUpdated Last updated timestamp
    /// @return isStale True if the price is stale
    function _isStale(uint256 _lastUpdated) internal view returns (bool isStale) {
        isStale = block.timestamp - _lastUpdated > getPriceOracleStorage().staleness;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { AccessControl } from "../../access/AccessControl.sol";

import { Delegation } from "../../delegation/Delegation.sol";
import { Lender } from "../../lendingPool/Lender.sol";
import { Oracle } from "../../oracle/Oracle.sol";

import { L2Token } from "../../token/L2Token.sol";
import { Vault } from "../../vault/Vault.sol";

import { PreMainnetVault } from "../../testnetCampaign/PreMainnetVault.sol";
import {
    ImplementationsConfig,
    InfraConfig,
    L2VaultConfig,
    PreMainnetInfraConfig,
    UsersConfig,
    VaultConfig
} from "../interfaces/DeployConfigs.sol";
import { LzAddressbook } from "../utils/LzUtils.sol";
import { ProxyUtils } from "../utils/ProxyUtils.sol";

contract DeployInfra is ProxyUtils {
    function _deployInfra(ImplementationsConfig memory implementations, UsersConfig memory users)
        internal
        returns (InfraConfig memory d)
    {
        // deploy proxy contracts
        d.accessControl = _proxy(implementations.accessControl);
        d.lender = _proxy(implementations.lender);
        d.oracle = _proxy(implementations.oracle);
        d.delegation = _proxy(implementations.delegation);

        // init infra instances
        AccessControl(d.accessControl).initialize(users.access_control_admin);
        Lender(d.lender).initialize(d.accessControl, d.delegation, d.oracle, 1.33e27, 1 hours, 1 days, 0.1e27, 0.91e27);
        Oracle(d.oracle).initialize(d.accessControl, 1 hours);
        Delegation(d.delegation).initialize(d.accessControl, d.oracle, 1 days);
    }

    function _deployPreMainnetInfra(
        LzAddressbook memory srcAddressbook,
        LzAddressbook memory dstAddressbook,
        address asset,
        uint48 maxCampaignLength
    ) internal returns (PreMainnetInfraConfig memory d) {
        d.preMainnetVault = address(
            new PreMainnetVault(asset, address(srcAddressbook.endpointV2), dstAddressbook.eid, maxCampaignLength)
        );
    }

    function _deployL2InfraForVault(
        UsersConfig memory users,
        VaultConfig memory l1Vault,
        LzAddressbook memory addressbook
    ) internal returns (L2VaultConfig memory d) {
        address lzEndpoint = address(addressbook.endpointV2);
        string memory name;
        string memory symbol;
        address delegate = users.vault_config_admin;

        name = Vault(l1Vault.capToken).name();
        symbol = Vault(l1Vault.capToken).symbol();
        d.bridgedCapToken = address(new L2Token(name, symbol, lzEndpoint, delegate));

        name = Vault(l1Vault.stakedCapToken).name();
        symbol = Vault(l1Vault.stakedCapToken).symbol();
        d.bridgedStakedCapToken = address(new L2Token(name, symbol, lzEndpoint, delegate));
    }
}


Vulnerability type: CWE-707|CWE-20|CWE-1288