Review and scan line by line the code, looking for any trace of vulnerabilities and find the correct vulnerability that is exist in this contract with depth understanding in solidity and in finding vulnerability and then confirm the vulnerability with evidence and give the vulnerable part that cause the bug with explaining and then give high-quality bug report /// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.15;

import "src/modules/PRICE/PRICE.v2.sol";

/// @title      OlympusPriceV2
/// @author     Oighty
/// @notice     Provides current and historical prices for assets
contract OlympusPricev2 is PRICEv2 {
    // DONE
    // [X] Update functions for asset price feeds, strategies, etc.
    // [X] Toggle MA on and off for an asset
    // [X] Add "store" functions that call a view function, store the result, and return the value
    // [X] Update add asset functions to account for new data structures
    // [X] Update existing view functions to use new data structures
    // [X] custom errors
    // [X] implementation details in function comments
    // [X] define and emit events: addAsset, removeAsset, update price feeds, update price strategy, update moving average

    // ========== CONSTRUCTOR ========== //

    /// @notice                         Constructor to create OlympusPrice V2
    ///
    /// @param kernel_                  Kernel address
    /// @param decimals_                Decimals that all prices will be returned with
    /// @param observationFrequency_    Frequency at which prices are stored for moving average
    constructor(Kernel kernel_, uint8 decimals_, uint32 observationFrequency_) Module(kernel_) {
        decimals = decimals_;
        observationFrequency = observationFrequency_;
    }

    // ========== KERNEL FUNCTIONS ========== //

    /// @inheritdoc Module
    function KEYCODE() public pure override returns (Keycode) {
        return toKeycode("PRICE");
    }

    /// @inheritdoc Module
    function VERSION() external pure override returns (uint8 major, uint8 minor) {
        major = 2;
        minor = 0;
    }

    // ========== MODIFIERS ========== //

    ////////////////////////////////////////////////////////////////
    //                      DATA FUNCTIONS                        //
    ////////////////////////////////////////////////////////////////

    // ========== ASSET INFORMATION ========== //

    /// @inheritdoc PRICEv2
    function getAssets() external view override returns (address[] memory) {
        return assets;
    }

    /// @inheritdoc PRICEv2
    function getAssetData(address asset_) external view override returns (Asset memory) {
        return _assetData[asset_];
    }

    // ========== ASSET PRICES ========== //

    /// @inheritdoc PRICEv2
    /// @dev        Optimistically uses the cached price if it has been updated this block, otherwise calculates price dynamically
    ///
    /// @dev        Will revert if:
    /// @dev        - `asset_` is not approved
    /// @dev        - No price could be determined
    function getPrice(address asset_) external view override returns (uint256) {
        // Try to use the last price, must be updated on the current timestamp
        // getPrice checks if asset is approved
        (uint256 price, uint48 timestamp) = getPrice(asset_, Variant.LAST);
        if (timestamp == uint48(block.timestamp)) return price;

        // If last price is stale, use the current price
        (price, ) = _getCurrentPrice(asset_);
        return price;
    }

    /// @inheritdoc PRICEv2
    /// @dev        Will revert if:
    /// @dev        - `asset_` is not approved
    /// @dev        - No price could be determined
    function getPrice(address asset_, uint48 maxAge_) external view override returns (uint256) {
        // Try to use the last price, must be updated more recently than maxAge
        // getPrice checks if asset is approved
        (uint256 price, uint48 timestamp) = getPrice(asset_, Variant.LAST);
        if (timestamp >= uint48(block.timestamp) - maxAge_) return price;

        // If last price is stale, use the current price
        (price, ) = _getCurrentPrice(asset_);
        return price;
    }

    /// @inheritdoc PRICEv2
    /// @dev        Will revert if:
    /// @dev        - `asset_` is not approved
    /// @dev        - No price could be determined
    /// @dev        - An invalid variant is requested
    function getPrice(
        address asset_,
        Variant variant_
    ) public view override returns (uint256 _price, uint48 _timestamp) {
        // Check if asset is approved
        if (!_assetData[asset_].approved) revert PRICE_AssetNotApproved(asset_);

        // Route to correct price function based on requested variant
        if (variant_ == Variant.CURRENT) {
            return _getCurrentPrice(asset_);
        } else if (variant_ == Variant.LAST) {
            return _getLastPrice(asset_);
        } else if (variant_ == Variant.MOVINGAVERAGE) {
            return _getMovingAveragePrice(asset_);
        }
    }

    /// @notice         Gets the current price of the asset
    /// @dev            This function follows this logic:
    /// @dev            - Get the price from each feed
    /// @dev            - If using the moving average, append the moving average to the results
    /// @dev            - If there is only one price and it is not zero, return it
    /// @dev            - Process the prices with the configured strategy
    ///
    /// @dev            Will revert if:
    /// @dev            - The resulting price is zero
    /// @dev            - The configured strategy cannot aggregate the prices
    ///
    /// @param asset_   Asset to get the price of
    /// @return         The price of the asset and the current block timestamp
    function _getCurrentPrice(address asset_) internal view returns (uint256, uint48) {
        Asset storage asset = _assetData[asset_];

        // Iterate through feeds to get prices to aggregate with strategy
        Component[] memory feeds = abi.decode(asset.feeds, (Component[]));
        uint256 numFeeds = feeds.length;
        uint256[] memory prices = asset.useMovingAverage
            ? new uint256[](numFeeds + 1)
            : new uint256[](numFeeds);
        uint8 _decimals = decimals; // cache in memory to save gas
        for (uint256 i; i < numFeeds; ) {
            (bool success_, bytes memory data_) = address(_getSubmoduleIfInstalled(feeds[i].target))
                .staticcall(
                    abi.encodeWithSelector(feeds[i].selector, asset_, _decimals, feeds[i].params)
                );

            // Store price if successful, otherwise leave as zero
            // Idea is that if you have several price calls and just
            // one fails, it'll DOS the contract with this revert.
            // We handle faulty feeds in the strategy contract.
            if (success_) prices[i] = abi.decode(data_, (uint256));

            unchecked {
                ++i;
            }
        }

        // If moving average is used in strategy, add to end of prices array
        if (asset.useMovingAverage) prices[numFeeds] = asset.cumulativeObs / asset.numObservations;

        // If there is only one price, ensure it is not zero and return
        // Otherwise, send to strategy to aggregate
        if (prices.length == 1) {
            if (prices[0] == 0) revert PRICE_PriceZero(asset_);
            return (prices[0], uint48(block.timestamp));
        } else {
            // Get price from strategy
            Component memory strategy = abi.decode(asset.strategy, (Component));
            (bool success, bytes memory data) = address(_getSubmoduleIfInstalled(strategy.target))
                .staticcall(abi.encodeWithSelector(strategy.selector, prices, strategy.params));

            // Ensure call was successful
            if (!success) revert PRICE_StrategyFailed(asset_, data);

            // Decode asset price
            uint256 price = abi.decode(data, (uint256));

            // Ensure value is not zero
            if (price == 0) revert PRICE_PriceZero(asset_);

            return (price, uint48(block.timestamp));
        }
    }

    /// @notice         Gets the last cached price of the asset
    /// @dev            This function follows this logic:
    /// @dev            - Get the last observation stored for the asset and return it
    ///
    /// @dev            If no price has been ever cached, this function will automatically
    /// @dev            return (0, 0).
    ///
    /// @param asset_   Asset to get the price of
    /// @return         The price of the asset and asset's last observation time
    function _getLastPrice(address asset_) internal view returns (uint256, uint48) {
        // Load asset data
        Asset memory asset = _assetData[asset_];

        // Get last observation stored for asset
        uint256 lastPrice = asset.obs[
            asset.nextObsIndex == 0 ? asset.numObservations - 1 : asset.nextObsIndex - 1
        ];

        // Last price doesn't have to be checked for zero because it is checked before being stored

        // Return last price and time
        return (lastPrice, asset.lastObservationTime);
    }

    /// @notice         Gets the moving average price of the asset
    /// @dev            This function follows this logic:
    /// @dev            - Calculate the moving average using the `cumulativeObs` / `numObservations`
    ///
    /// @dev            Will revert if:
    /// @dev            - The moving average is not stored for the asset
    ///
    /// @param asset_   Asset to get the price of
    /// @return         The price of the asset and asset's last observation time
    function _getMovingAveragePrice(address asset_) internal view returns (uint256, uint48) {
        // Load asset data
        Asset memory asset = _assetData[asset_];

        // Check if moving average is stored for asset
        if (!asset.storeMovingAverage) revert PRICE_MovingAverageNotStored(asset_);

        // Calculate moving average
        uint256 movingAverage = asset.cumulativeObs / asset.numObservations;

        // Moving average doesn't have to be checked for zero because each value is checked before being stored

        // Return moving average and time
        return (movingAverage, asset.lastObservationTime);
    }

    /// @inheritdoc PRICEv2
    /// @dev        Optimistically uses the cached price if it has been updated this block, otherwise calculates price dynamically
    function getPriceIn(address asset_, address base_) external view override returns (uint256) {
        // Get the last price of each asset (getPrice checks if asset is approved)
        (uint256 assetPrice, uint48 assetTime) = getPrice(asset_, Variant.LAST);
        (uint256 basePrice, uint48 baseTime) = getPrice(base_, Variant.LAST);

        // Try to use the last prices, timestamp must be current
        // If stale, get current price
        if (assetTime != uint48(block.timestamp)) {
            (assetPrice, ) = _getCurrentPrice(asset_);
        }
        if (baseTime != uint48(block.timestamp)) {
            (basePrice, ) = _getCurrentPrice(base_);
        }

        // Calculate the price of the asset in the base and return
        return (assetPrice * 10 ** decimals) / basePrice;
    }

    /// @inheritdoc PRICEv2
    function getPriceIn(
        address asset_,
        address base_,
        uint48 maxAge_
    ) external view override returns (uint256) {
        // Get the last price of each asset (getPrice checks if asset is approved)
        (uint256 assetPrice, uint48 assetTime) = getPrice(asset_, Variant.LAST);
        (uint256 basePrice, uint48 baseTime) = getPrice(base_, Variant.LAST);

        // Try to use the last prices, timestamp must be no older than maxAge_
        // If stale, get current price
        if (assetTime < uint48(block.timestamp) - maxAge_) {
            (assetPrice, ) = _getCurrentPrice(asset_);
        }
        if (baseTime < uint48(block.timestamp) - maxAge_) {
            (basePrice, ) = _getCurrentPrice(base_);
        }

        // Calculate the price of the asset in the base and return
        return (assetPrice * 10 ** decimals) / basePrice;
    }

    /// @inheritdoc PRICEv2
    function getPriceIn(
        address asset_,
        address base_,
        Variant variant_
    ) external view override returns (uint256, uint48) {
        // Get the price of the asset (checks if approved)
        (uint256 assetPrice, uint48 assetTime) = getPrice(asset_, variant_);

        // Get the price of the base (checks if approved)
        (uint256 basePrice, uint48 baseTime) = getPrice(base_, variant_);

        // The updatedAt timestamp is the minimum of the two price updatedAt timestamps
        uint48 updatedAt = assetTime < baseTime ? assetTime : baseTime;

        // Calculate the price of the asset in the base
        uint256 price = (assetPrice * 10 ** decimals) / basePrice;

        return (price, updatedAt);
    }

    /// @inheritdoc PRICEv2
    /// @dev        Implements the following logic:
    ///             - Get the current price using `_getCurrentPrice()`
    ///             - Store the price in the asset's observation array at the index corresponding to the asset's value of `nextObsIndex`
    ///             - Updates the asset's `lastObservationTime` to the current block timestamp
    ///             - Increments the asset's `nextObsIndex` by 1, wrapping around to 0 if necessary
    ///             - If the asset is configured to store the moving average, update the `cumulativeObs` value subtracting the previous value and adding the new one
    ///             - Emit a `PriceStored` event
    ///
    ///             Will revert if:
    ///             - The asset is not approved
    ///             - The caller is not permissioned
    ///             - The price was not able to be determined
    function storePrice(address asset_) public override permissioned {
        Asset storage asset = _assetData[asset_];

        // Check if asset is approved
        if (!asset.approved) revert PRICE_AssetNotApproved(asset_);

        // Get the current price for the asset
        (uint256 price, uint48 currentTime) = _getCurrentPrice(asset_);

        // Store the data in the obs index
        uint256 oldestPrice = asset.obs[asset.nextObsIndex];
        asset.obs[asset.nextObsIndex] = price;

        // Update the last observation time and increment the next index
        asset.lastObservationTime = currentTime;
        asset.nextObsIndex = (asset.nextObsIndex + 1) % asset.numObservations;

        // Update the cumulative observation, if storing the moving average
        if (asset.storeMovingAverage)
            asset.cumulativeObs = asset.cumulativeObs + price - oldestPrice;

        // Emit event
        emit PriceStored(asset_, price, currentTime);
    }

    // ========== ASSET MANAGEMENT ========== //

    /// @inheritdoc PRICEv2
    /// @dev        Implements the following logic:
    /// @dev        - Performs basic checks on the parameters
    /// @dev        - Sets the price strategy using `_updateAssetPriceStrategy()`
    /// @dev        - Sets the price feeds using `_updateAssetPriceFeeds()`
    /// @dev        - Sets the moving average data using `_updateAssetMovingAverage()`
    /// @dev        - Validates the configuration using `_getCurrentPrice()`, which will revert if there is a mis-configuration
    /// @dev        - Adds the asset to the `assets` array and marks it as approved
    ///
    /// @dev        Will revert if:
    /// @dev        - The caller is not permissioned
    /// @dev        - `asset_` is not a contract
    /// @dev        - `asset_` is already approved
    /// @dev        - The moving average is being used, but not stored
    /// @dev        - An empty strategy was specified, but the number of feeds requires a strategy
    function addAsset(
        address asset_,
        bool storeMovingAverage_,
        bool useMovingAverage_,
        uint32 movingAverageDuration_,
        uint48 lastObservationTime_,
        uint256[] memory observations_,
        Component memory strategy_,
        Component[] memory feeds_
    ) external override permissioned {
        // Check that asset is a contract
        if (asset_.code.length == 0) revert PRICE_AssetNotContract(asset_);

        Asset storage asset = _assetData[asset_];

        // Ensure asset is not already added
        if (asset.approved) revert PRICE_AssetAlreadyApproved(asset_);

        // If not storing the moving average, validate that it's not being used by the strategy
        if (useMovingAverage_ && !storeMovingAverage_)
            revert PRICE_ParamsStoreMovingAverageRequired(asset_);

        // Strategy cannot be zero if number of feeds + useMovingAverage is greater than 1
        if (
            (feeds_.length + (useMovingAverage_ ? 1 : 0)) > 1 &&
            fromSubKeycode(strategy_.target) == bytes20(0)
        )
            revert PRICE_ParamsStrategyInsufficient(
                asset_,
                abi.encode(strategy_),
                feeds_.length,
                useMovingAverage_
            );

        // Update asset strategy data
        _updateAssetPriceStrategy(asset_, strategy_, useMovingAverage_);

        // Update asset price feed data
        _updateAssetPriceFeeds(asset_, feeds_);

        // Update asset moving average data
        _updateAssetMovingAverage(
            asset_,
            storeMovingAverage_,
            movingAverageDuration_,
            lastObservationTime_,
            observations_
        );

        // Validate configuration
        _getCurrentPrice(asset_);

        // Set asset as approved and add to array
        asset.approved = true;
        assets.push(asset_);

        // Emit event
        emit AssetAdded(asset_);
    }

    /// @inheritdoc PRICEv2
    /// @dev        Will revert if:
    /// @dev        - `asset_` is not approved
    /// @dev        - The caller is not permissioned
    function removeAsset(address asset_) external override permissioned {
        // Ensure asset is already added
        if (!_assetData[asset_].approved) revert PRICE_AssetNotApproved(asset_);

        // Remove asset from array
        uint256 len = assets.length;
        for (uint256 i; i < len; ) {
            if (assets[i] == asset_) {
                assets[i] = assets[len - 1];
                assets.pop();
                break;
            }
            unchecked {
                ++i;
            }
        }

        // Remove asset from mapping
        delete _assetData[asset_];

        // Emit event
        emit AssetRemoved(asset_);
    }

    /// @inheritdoc PRICEv2
    /// @dev        Implements the following logic:
    /// @dev        - Performs basic checks on the parameters
    /// @dev        - Sets the price feeds using `_updateAssetPriceFeeds()`
    /// @dev        - Validates the configuration using `_getCurrentPrice()`, which will revert if there is a mis-configuration
    ///
    /// @dev        Will revert if:
    /// @dev        - `asset_` is not approved
    /// @dev        - The caller is not permissioned
    /// @dev        - `_updateAssetPriceFeeds()` reverts
    /// @dev        - `_getCurrentPrice()` reverts
    function updateAssetPriceFeeds(
        address asset_,
        Component[] memory feeds_
    ) external override permissioned {
        // Ensure asset is already added
        if (!_assetData[asset_].approved) revert PRICE_AssetNotApproved(asset_);

        _updateAssetPriceFeeds(asset_, feeds_);

        // Validate the configuration
        _getCurrentPrice(asset_);

        // Emit event
        emit AssetPriceFeedsUpdated(asset_);
    }

    /// @notice         Updates the price feeds for the asset
    /// @dev            Implements the following logic:
    /// @dev            - Performs basic checks on the parameters
    /// @dev            - Sets the price feeds for the asset
    ///
    /// @dev            Will revert if:
    /// @dev            - The number of feeds is zero
    /// @dev            - Any feed has a submodule that is not installed
    ///
    /// @param asset_   Asset to update the price feeds for
    /// @param feeds_   Array of price feed components
    function _updateAssetPriceFeeds(address asset_, Component[] memory feeds_) internal {
        // Validate feed component submodules are installed and update feed array
        uint256 len = feeds_.length;
        if (len == 0) revert PRICE_ParamsPriceFeedInsufficient(asset_, len, 1);

        bytes32[] memory hashes = new bytes32[](len);

        for (uint256 i; i < len; ) {
            // Check that the submodule is installed
            if (!_submoduleIsInstalled(feeds_[i].target))
                revert PRICE_SubmoduleNotInstalled(asset_, abi.encode(feeds_[i].target));

            // Confirm that the feed is not a duplicate by checking the hash against hashes of previous feeds in the array
            bytes32 _hash = keccak256(
                abi.encode(feeds_[i].target, feeds_[i].selector, feeds_[i].params)
            );

            for (uint256 j; j < i; ) {
                if (_hash == hashes[j]) revert PRICE_DuplicatePriceFeed(asset_, i);
                unchecked {
                    ++j;
                }
            }

            hashes[i] = _hash;

            unchecked {
                ++i;
            }
        }

        _assetData[asset_].feeds = abi.encode(feeds_);
    }

    /// @inheritdoc PRICEv2
    /// @dev        Implements the following logic:
    /// @dev        - Performs basic checks on the parameters
    /// @dev        - Sets the price strategy using `_updateAssetPriceStrategy()`
    /// @dev        - Validates the configuration using `_getCurrentPrice()`, which will revert if there is a mis-configuration
    ///
    /// @dev        Will revert if:
    /// @dev        - `asset_` is not approved
    /// @dev        - The caller is not permissioned
    /// @dev        - The moving average is used, but is not stored
    /// @dev        - An empty strategy was specified, but the number of feeds requires a strategy
    function updateAssetPriceStrategy(
        address asset_,
        Component memory strategy_,
        bool useMovingAverage_
    ) external override permissioned {
        // Ensure asset is already added
        if (!_assetData[asset_].approved) revert PRICE_AssetNotApproved(asset_);

        // Validate that the moving average is stored for the asset to use in strategy
        if (useMovingAverage_ && !_assetData[asset_].storeMovingAverage)
            revert PRICE_ParamsStoreMovingAverageRequired(asset_);

        // Strategy cannot be zero if number of feeds + useMovingAverage is greater than 1
        Component[] memory feeds = abi.decode(_assetData[asset_].feeds, (Component[]));
        if (
            (feeds.length + (useMovingAverage_ ? 1 : 0)) > 1 &&
            fromSubKeycode(strategy_.target) == bytes20(0)
        )
            revert PRICE_ParamsStrategyInsufficient(
                asset_,
                abi.encode(strategy_),
                feeds.length,
                useMovingAverage_
            );

        _updateAssetPriceStrategy(asset_, strategy_, useMovingAverage_);

        // Validate
        _getCurrentPrice(asset_);

        // Emit event
        emit AssetPriceStrategyUpdated(asset_);
    }

    /// @notice                     Updates the price strategy for the asset
    /// @dev                        Implements the following logic:
    /// @dev                        - Performs basic checks on the parameters
    /// @dev                        - Sets the price strategy for the asset
    /// @dev                        - Sets the `useMovingAverage` flag for the asset
    ///
    /// @dev                        Will revert if:
    /// @dev                        - The submodule used by the strategy is not installed
    ///
    /// @param asset_               Asset to update the price strategy for
    /// @param strategy_            Price strategy component
    /// @param useMovingAverage_    Flag to indicate if the moving average should be used in the strategy
    function _updateAssetPriceStrategy(
        address asset_,
        Component memory strategy_,
        bool useMovingAverage_
    ) internal {
        // Validate strategy component submodule is installed (if a strategy is being used)
        // A strategy is optional if there is only one price feed being used.
        // The number of feeds is checked in the external functions that call this one.
        if (
            fromSubKeycode(strategy_.target) != bytes20(0) &&
            !_submoduleIsInstalled(strategy_.target)
        ) revert PRICE_SubmoduleNotInstalled(asset_, abi.encode(strategy_.target));

        // Update the asset price strategy
        _assetData[asset_].strategy = abi.encode(strategy_);

        // Update whether the strategy uses a moving average (should be checked that the moving average is stored for the asset prior to sending to this function)
        _assetData[asset_].useMovingAverage = useMovingAverage_;
    }

    /// @inheritdoc                     PRICEv2
    /// @dev                            Implements the following logic:
    /// @dev                            - Performs basic checks on the parameters
    /// @dev                            - Sets the moving average data using `_updateAssetMovingAverage()`
    ///
    /// @dev                            Will revert if:
    /// @dev                            - `asset_` is not approved
    /// @dev                            - The caller is not permissioned
    /// @dev                            - The moving average is used, but is not stored
    ///
    /// @param asset_                   Asset to update the moving average data for
    /// @param storeMovingAverage_      Flag to indicate if the moving average should be stored
    /// @param movingAverageDuration_   Duration of the moving average
    /// @param lastObservationTime_     Timestamp of the last observation
    /// @param observations_            Array of observations to store
    function updateAssetMovingAverage(
        address asset_,
        bool storeMovingAverage_,
        uint32 movingAverageDuration_,
        uint48 lastObservationTime_,
        uint256[] memory observations_
    ) external override permissioned {
        // Ensure asset is already added
        if (!_assetData[asset_].approved) revert PRICE_AssetNotApproved(asset_);

        // If not storing the moving average, validate that it's not being used by the strategy.
        // If it is, then you are moving from storing a moving average to not storing a moving average.
        // First, change the strategy to not use the moving average, then update the moving average data.
        if (_assetData[asset_].useMovingAverage && !storeMovingAverage_)
            revert PRICE_ParamsStoreMovingAverageRequired(asset_);

        _updateAssetMovingAverage(
            asset_,
            storeMovingAverage_,
            movingAverageDuration_,
            lastObservationTime_,
            observations_
        );

        // Emit event
        emit AssetMovingAverageUpdated(asset_);
    }

    /// @notice                         Updates the moving average data for the asset
    /// @dev                            Implements the following logic:
    /// @dev                            - Removes existing moving average data
    /// @dev                            - Performs basic checks on the parameters
    /// @dev                            - Sets the moving average data for the asset
    /// @dev                            - If the moving average is not stored, gets the current price and stores it so that every asset has at least one cached value
    ///
    /// @dev                            Will revert if:
    /// @dev                            - `lastObservationTime_` is in the future
    /// @dev                            - Any observation is zero
    /// @dev                            - The number of observations provided is insufficient
    ///
    /// @param asset_                   Asset to update the moving average data for
    /// @param storeMovingAverage_      Flag to indicate if the moving average should be stored
    /// @param movingAverageDuration_   Duration of the moving average
    /// @param lastObservationTime_     Timestamp of the last observation
    /// @param observations_            Array of observations to store
    function _updateAssetMovingAverage(
        address asset_,
        bool storeMovingAverage_,
        uint32 movingAverageDuration_,
        uint48 lastObservationTime_,
        uint256[] memory observations_
    ) internal {
        Asset storage asset = _assetData[asset_];

        // Remove existing cached or moving average data, if any
        if (asset.obs.length > 0) delete asset.obs;

        // Ensure last observation time is not in the future
        if (lastObservationTime_ > block.timestamp)
            revert PRICE_ParamsLastObservationTimeInvalid(
                asset_,
                lastObservationTime_,
                0,
                uint48(block.timestamp)
            );

        if (storeMovingAverage_) {
            // If storing a moving average, validate params
            if (movingAverageDuration_ == 0 || movingAverageDuration_ % observationFrequency != 0)
                revert PRICE_ParamsMovingAverageDurationInvalid(
                    asset_,
                    movingAverageDuration_,
                    observationFrequency
                );

            uint16 numObservations = uint16(movingAverageDuration_ / observationFrequency);
            if (observations_.length != numObservations)
                revert PRICE_ParamsInvalidObservationCount(
                    asset_,
                    observations_.length,
                    numObservations,
                    numObservations
                );

            asset.storeMovingAverage = true;

            asset.movingAverageDuration = movingAverageDuration_;
            asset.nextObsIndex = 0;
            asset.numObservations = numObservations;
            asset.lastObservationTime = lastObservationTime_;
            asset.cumulativeObs = 0; // reset to zero before adding new observations
            for (uint256 i; i < numObservations; ) {
                if (observations_[i] == 0) revert PRICE_ParamsObservationZero(asset_, i);

                asset.cumulativeObs += observations_[i];
                asset.obs.push(observations_[i]);
                unchecked {
                    ++i;
                }
            }

            // Emit Price Stored event for new cached value
            emit PriceStored(asset_, observations_[numObservations - 1], lastObservationTime_);
        } else {
            // If not storing the moving average, validate that the array has at most one value (for caching)
            if (observations_.length > 1)
                revert PRICE_ParamsInvalidObservationCount(asset_, observations_.length, 0, 1);

            asset.storeMovingAverage = false;
            asset.movingAverageDuration = 0;
            asset.nextObsIndex = 0;
            asset.numObservations = 1;
            if (observations_.length == 0) {
                // If no observation provided, get the current price and store it
                // We can do this here because we know the moving average isn't being stored
                // and therefore, it is not being used in the strategy to calculate the price
                (uint256 currentPrice, uint48 timestamp) = _getCurrentPrice(asset_);
                asset.obs.push(currentPrice);
                asset.lastObservationTime = timestamp;

                // Emit Price Stored event for new cached value
                emit PriceStored(asset_, currentPrice, timestamp);
            } else {
                // If an observation is provided, validate it and store it
                if (observations_[0] == 0) revert PRICE_ParamsObservationZero(asset_, 0);

                asset.obs.push(observations_[0]);
                asset.lastObservationTime = lastObservationTime_;

                // Emit Price Stored event for new cached value
                emit PriceStored(asset_, observations_[0], lastObservationTime_);
            }

            // We don't track cumulativeObs when not storing the moving average, even though there is one data point in the array for caching
            asset.cumulativeObs = 0;
        }
    }
}