here here https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L102C3-L130C1 here is used to calculates the latest TWAP value without considering the timeliness   and here https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L131C4-L175C1  to compares the Chainlink answer to the TWAP value without any validation of the data's freshness and this can cause  a problem leading  in incorrect financial operations and exposing the protocol to be in risk of manipulation in this contract // SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.24;

import "./Declarations.sol";

abstract contract OracleHelper is Declarations {

    /**
     * @dev Adds priceFeed for a given token.
     */
    function _addOracle(
        address _tokenAddress,
        IPriceFeed _priceFeedAddress,
        address[] calldata _underlyingFeedTokens
    )
        internal
    {
        if (priceFeed[_tokenAddress] > ZERO_FEED) {
            revert OracleAlreadySet();
        }

        priceFeed[_tokenAddress] = _priceFeedAddress;

        _tokenDecimals[_tokenAddress] = IERC20(
            _tokenAddress
        ).decimals();

        underlyingFeedTokens[_tokenAddress] = _underlyingFeedTokens;
    }

    function _addAggregator(
        address _tokenAddress
    )
        internal
    {
        IAggregator tokenAggregator = IAggregator(
            priceFeed[_tokenAddress].aggregator()
        );

        if (tokenAggregatorFromTokenAddress[_tokenAddress] > ZERO_AGGREGATOR) {
            revert AggregatorAlreadySet();
        }

        if (_checkFunctionExistence(address(tokenAggregator)) == false) {
            revert FunctionDoesntExist();
        }

        UniTwapPoolInfo memory uniTwapPoolInfoStruct = uniTwapPoolInfo[
            _tokenAddress
        ];

        if (uniTwapPoolInfoStruct.oracle > ZERO_ADDRESS) {
            revert AggregatorNotNecessary();
        }

        tokenAggregatorFromTokenAddress[_tokenAddress] = tokenAggregator;
    }

    function _checkFunctionExistence(
        address _tokenAggregator
    )
        internal
        returns (bool)
    {
        uint256 size;

        assembly {
            size := extcodesize(
                _tokenAggregator
            )
        }

        if (size == 0) {
            return false;
        }

        (bool success, ) = _tokenAggregator.call(
            abi.encodeWithSignature(
                "maxAnswer()"
            )
        );

        return success;
    }

    function _compareMinMax(
        IAggregator _tokenAggregator,
        int192 _answer
    )
        internal
        view
    {
        int192 maxAnswer = _tokenAggregator.maxAnswer();
        int192 minAnswer = _tokenAggregator.minAnswer();

        if (_answer > maxAnswer || _answer < minAnswer) {
            revert OracleIsDead();
        }
    }

    /**
     * @dev Returns Twaps latest USD value
     * by passing the underlying token address.
     */
    function latestResolverTwap(
        address _tokenAddress
    )
        public
        view
        returns (uint256)
    {
        UniTwapPoolInfo memory uniTwapPoolInfoStruct = uniTwapPoolInfo[
            _tokenAddress
        ];

        if (uniTwapPoolInfoStruct.isUniPool == true) {

            return _getTwapPrice(
                _tokenAddress,
                uniTwapPoolInfoStruct.oracle
            ) / 10 ** (_decimalsWETH - decimals(_tokenAddress));
        }

        return _getTwapDerivatePrice(
            _tokenAddress,
            uniTwapPoolInfoStruct
        ) / 10 ** (_decimalsWETH - decimals(_tokenAddress));
    }

    function _validateAnswer(
        address _tokenAddress
    )
        internal
        view
        returns (uint256)
    {
        UniTwapPoolInfo memory uniTwapPoolInfoStruct = uniTwapPoolInfo[
            _tokenAddress
        ];

        uint256 fetchTwapValue;

        if (uniTwapPoolInfoStruct.oracle > ZERO_ADDRESS) {
            fetchTwapValue = latestResolverTwap(
                _tokenAddress
            );
        }

        uint256 answer = _getChainlinkAnswer(
            _tokenAddress
        );

        if (tokenAggregatorFromTokenAddress[_tokenAddress] > ZERO_AGGREGATOR) {
            _compareMinMax(
                tokenAggregatorFromTokenAddress[_tokenAddress],
                int192(uint192(answer))
            );
        }

        if (fetchTwapValue > 0) {

            uint256 relativeDifference = _getRelativeDifference(
                answer,
                fetchTwapValue
            );

            _compareDifference(
                relativeDifference
            );
        }

        return answer;
    }

    /**
     * @dev Adds uniTwapPoolInfo for a given token.
     */
    function _writeUniTwapPoolInfoStruct(
        address _tokenAddress,
        address _oracle,
        bool _isUniPool
    )
        internal
    {
        uniTwapPoolInfo[_tokenAddress] = UniTwapPoolInfo({
            oracle: _oracle,
            isUniPool: _isUniPool
        });
    }

    /**
     * @dev Adds uniTwapPoolInfo for a given token and its derivative.
     */
    function _writeUniTwapPoolInfoStructDerivative(
        address _tokenAddress,
        address _partnerTokenAddress,
        address _oracleAddress,
        address _partnerOracleAddress,
        bool _isUniPool
    )
        internal
    {
        _writeUniTwapPoolInfoStruct(
            _tokenAddress,
            _oracleAddress,
            _isUniPool
        );

        derivativePartnerTwap[_tokenAddress] = DerivativePartnerInfo(
            _partnerTokenAddress,
            _partnerOracleAddress
        );
    }

    function _getRelativeDifference(
        uint256 _answerUint256,
        uint256 _fetchTwapValue
    )
        internal
        pure
        returns (uint256)
    {
        if (_answerUint256 > _fetchTwapValue) {
            return _answerUint256
                * PRECISION_FACTOR_E4
                / _fetchTwapValue;
        }

        return _fetchTwapValue
            * PRECISION_FACTOR_E4
            / _answerUint256;
    }

    function _compareDifference(
        uint256 _relativeDifference
    )
        internal
        view
    {
        if (_relativeDifference > ALLOWED_DIFFERENCE) {
            revert OraclesDeviate();
        }
    }

    function _getChainlinkAnswer(
        address _tokenAddress
    )
        internal
        view
        returns (uint256)
    {
        (
            ,
            int256 answer,
            ,
            ,
        ) = priceFeed[_tokenAddress].latestRoundData();

        return uint256(
            answer
        );
    }

    function getETHPriceInUSD()
        public
        view
        returns (uint256)
    {
        if (_chainLinkIsDead(ETH_USD_PLACEHOLDER) == true) {
            revert OracleIsDead();
        }

        return _validateAnswer(
            ETH_USD_PLACEHOLDER
        );
    }

    /**
    * @dev Retrieves the pool address for given
    * tokens and fee from Uniswap V3 Factory.
    */
    function _getPool(
        address _token0,
        address _token1,
        uint24 _fee
    )
        internal
        view
        returns (address pool)
    {
        return UNI_V3_FACTORY.getPool(
            _token0,
            _token1,
            _fee
        );
    }

    /**
    * @dev Validates if the given token address
    * is one of the two specified token addresses.
    */
    function _validateTokenAddress(
        address _tokenAddress,
        address _token0,
        address _token1
    )
        internal
        pure
    {
        if (_tokenAddress == ZERO_ADDRESS) {
            revert ZeroAddressNotAllowed();
        }

        if (_tokenAddress != _token0 && _tokenAddress != _token1) {
            revert TokenAddressMismatch();
        }
    }

    /**
    * @dev Validates if the given pool
    * address matches the expected pool address.
    */
    function _validatePoolAddress(
        address _pool,
        address _expectedPool
    )
        internal
        pure
    {
        if (_pool == ZERO_ADDRESS) {
            revert PoolDoesNotExist();
        }

        if (_pool != _expectedPool) {
            revert PoolAddressMismatch();
        }
    }

    /**
    * @dev Validates if the price feed for
    * a given token address is set.
    */
    function _validatePriceFeed(
        address _tokenAddress
    )
        internal
        view
    {
        if (priceFeed[_tokenAddress] == ZERO_FEED) {
            revert ChainLinkOracleNotSet();
        }
    }

    /**
    * @dev Validates if the TWAP oracle for
    * a given token address is already set.
    */
    function _validateTwapOracle(
        address _tokenAddress
    )
        internal
        view
    {
        if (uniTwapPoolInfo[_tokenAddress].oracle > ZERO_ADDRESS) {
            revert TwapOracleAlreadySet();
        }
    }

    /**
     * @dev Returns twapPrice by passing
     * the underlying token address.
     */
    function _getTwapPrice(
        address _tokenAddress,
        address _oracle
    )
        internal
        view
        returns (uint256)
    {
        return OracleLibrary.getQuoteAtTick(
            _getAverageTick(
                _oracle
            ),
            _getOneUnit(
                _tokenAddress
            ),
            _tokenAddress,
            WETH_ADDRESS
        );
    }

    function _getOneUnit(
        address _tokenAddress
    )
        internal
        view
        returns (uint128)
    {
        return uint128(
            10 ** _tokenDecimals[_tokenAddress]
        );
    }

    function _getAverageTick(
        address _oracle
    )
        internal
        view
        returns (int24)
    {
        uint32[] memory secondsAgo = new uint32[](
            2
        );

        secondsAgo[0] = TWAP_PERIOD;
        secondsAgo[1] = 0;

        (
            int56[] memory tickCumulatives
            ,
        ) = IUniswapV3Pool(_oracle).observe(
            secondsAgo
        );

        int56 twapPeriodInt56 = int56(
            int32(TWAP_PERIOD)
        );

        int56 tickCumulativesDelta = tickCumulatives[1]
            - tickCumulatives[0];

        int24 tick = int24(
            tickCumulativesDelta
            / twapPeriodInt56
        );

        if (tickCumulativesDelta < 0 && (tickCumulativesDelta % twapPeriodInt56 != 0)) {
            tick--;
        }

        return tick;
    }

    /**
     * @dev Returns priceFeed decimals by
     * passing the underlying token address.
     */
    function decimals(
        address _tokenAddress
    )
        public
        view
        returns (uint8)
    {
        return priceFeed[_tokenAddress].decimals();
    }

    function _getTwapDerivatePrice(
        address _tokenAddress,
        UniTwapPoolInfo memory _uniTwapPoolInfo
    )
        internal
        view
        returns (uint256)
    {
        DerivativePartnerInfo memory partnerInfo = derivativePartnerTwap[
            _tokenAddress
        ];

        uint256 firstQuote = OracleLibrary.getQuoteAtTick(
            _getAverageTick(
                _uniTwapPoolInfo.oracle
            ),
            _getOneUnit(
                partnerInfo.partnerTokenAddress
            ),
            partnerInfo.partnerTokenAddress,
            WETH_ADDRESS
        );

        uint256 secondQuote = OracleLibrary.getQuoteAtTick(
            _getAverageTick(
                partnerInfo.partnerOracleAddress
            ),
            _getOneUnit(
                _tokenAddress
            ),
            _tokenAddress,
            partnerInfo.partnerTokenAddress
        );

        return firstQuote
            * secondQuote
            / uint256(
                _getOneUnit(
                    partnerInfo.partnerTokenAddress
                )
            );
    }

    /**
     * @dev Stores expected heartbeat
     * value for a pricing feed token.
     */
    function _recalibrate(
        address _tokenAddress
    )
        internal
    {
        heartBeat[_tokenAddress] = _recalibratePreview(
            _tokenAddress
        );
    }

    /**
     * @dev Check if chainLink
     * squencer is wroking.
     */
    function sequencerIsDead()
        public
        view
        returns (bool)
    {
        if (IS_ARBITRUM_CHAIN == false) {
            return false;
        }

        (
            ,
            int256 answer,
            uint256 startedAt,
            ,
        ) = SEQUENCER.latestRoundData();

        if (answer == 1) {
            return true;
        }

        uint256 timeSinceUp = block.timestamp
            - startedAt;

        if (timeSinceUp <= GRACE_PEROID) {
            return true;
        }

        return false;
    }

    /**
     * @dev Check if chainLink feed was
     * updated within expected timeFrame
     * for single {_tokenAddress}.
     */
    function _chainLinkIsDead(
        address _tokenAddress
    )
        internal
        view
        returns (bool)
    {
        // @TODO: Add a check for TWAP comparison on 2.5%
        // if TWAP exists for the token.

        if (heartBeat[_tokenAddress] == 0) {
            revert HeartBeatNotSet();
        }

        uint80 latestRoundId = getLatestRoundId(
            _tokenAddress
        );

        uint256 upd = _getRoundTimestamp(
            _tokenAddress,
            latestRoundId
        );

        unchecked {
            upd = block.timestamp < upd
                ? block.timestamp
                : block.timestamp - upd;

            return upd > heartBeat[_tokenAddress];
        }
    }

    /**
     * @dev Recalibrates expected
     * heartbeat for a pricing feed.
     */
    function _recalibratePreview(
        address _tokenAddress
    )
        internal
        view
        returns (uint256)
    {
        uint80 latestRoundId = getLatestRoundId(
            _tokenAddress
        );

        uint256 latestTimestamp = _getRoundTimestamp(
            _tokenAddress,
            latestRoundId
        );

        uint80 iterationCount = _getIterationCount(
            latestRoundId
        );

        if (iterationCount < MIN_ITERATION_COUNT) {
            revert SampleTooSmall(
                {
                    size: iterationCount
                }
            );
        }

        uint80 i = 1;
        uint256 currentDiff;
        uint256 currentBiggest;
        uint256 currentSecondBiggest;

        while (i < iterationCount) {

            uint256 currentTimestamp = _getRoundTimestamp(
                _tokenAddress,
                latestRoundId - i
            );

            currentDiff = latestTimestamp
                - currentTimestamp;

            latestTimestamp = currentTimestamp;

            if (currentDiff >= currentBiggest) {

                currentSecondBiggest = currentBiggest;
                currentBiggest = currentDiff;

            } else if (currentDiff > currentSecondBiggest) {
                currentSecondBiggest = currentDiff;
            }

            unchecked {
                ++i;
            }
        }

        return currentSecondBiggest;
    }

    /**
     * @dev Determines number of iterations
     * needed during heartbeat recalibration.
     */
    function _getIterationCount(
        uint80 _latestAggregatorRoundId
    )
        internal
        pure
        returns (uint80 res)
    {
        res = _latestAggregatorRoundId < MAX_ROUND_COUNT
            ? _latestAggregatorRoundId
            : MAX_ROUND_COUNT;
    }

    /**
     * @dev Fetches timestamp of a byteshifted
     * aggregatorRound with specific _roundId.
     */
    function _getRoundTimestamp(
        address _tokenAddress,
        uint80 _roundId
    )
        internal
        view
        returns (uint256)
    {
        (
            ,
            ,
            ,
            uint256 timestamp
            ,
        ) = priceFeed[_tokenAddress].getRoundData(
                _roundId
            );

        return timestamp;
    }

    /**
     * @dev Routing latest round data from chainLink.
     * Returns latestRoundData by passing underlying token address.
     */
    function getLatestRoundId(
        address _tokenAddress
    )
        public
        view
        returns (
            uint80 roundId
        )
    {
        (
            roundId
            ,
            ,
            ,
            ,
        ) = priceFeed[_tokenAddress].latestRoundData();
    }
} i fuzz with this test import random

# Constants for simulation
DECIMALS_WETH = 18
PRECISION_FACTOR_E4 = 10000
ALLOWED_DIFFERENCE = 500  # 5% allowed difference for simplicity

# Mock data structures
class UniTwapPoolInfo:
    def __init__(self, oracle, isUniPool):
        self.oracle = oracle
        self.isUniPool = isUniPool

# Mock functions
def get_twap_price(token_address, oracle):
    # Simulate fetching a TWAP price
    # For simplicity, randomize to represent fluctuating market conditions
    return random.randint(1, 1000) * 10 ** DECIMALS_WETH

def get_chainlink_answer(token_address):
    # Simulate fetching a Chainlink price
    # Again, randomizing to simulate real market conditions
    return random.randint(1, 1000) * 10 ** DECIMALS_WETH

def get_relative_difference(answer, fetchTwapValue):
    if answer > fetchTwapValue:
        return answer * PRECISION_FACTOR_E4 / fetchTwapValue
    return fetchTwapValue * PRECISION_FACTOR_E4 / answer

def compare_difference(relative_difference):
    return relative_difference > ALLOWED_DIFFERENCE

# Simulate fetching and comparing prices without validating data freshness
def simulate_price_comparison(token_address):
    uniTwapPoolInfoStruct = UniTwapPoolInfo(oracle="MockOracle", isUniPool=True)
    fetchTwapValue = get_twap_price(token_address, uniTwapPoolInfoStruct.oracle)
    answer = get_chainlink_answer(token_address)
    relative_difference = get_relative_difference(answer, fetchTwapValue)
    deviation_detected = compare_difference(relative_difference)
    return deviation_detected, relative_difference / PRECISION_FACTOR_E4

# Fuzz testing with a range of conditions
def fuzz_test(iterations=1000):
    deviations = 0
    for _ in range(iterations):
        token_address = "MockToken"
        deviation_detected, relative_diff = simulate_price_comparison(token_address)
        if deviation_detected:
            deviations += 1
    return deviations, iterations

# Run fuzz test
fuzz_test_result = fuzz_test()
fuzz_test_result
 and i get this result To mimic the behavior of the contract functions latestResolverTwap and _validateAnswer and then perform fuzz testing to assess the vulnerability regarding reliance on potentially outdated oracle data, we'll need to set up a simplified Python simulation. This simulation will represent the logic of fetching oracle data, comparing TWAP (Time-Weighted Average Price) values to Chainlink answers, and checking for significant deviations without validating data freshness or operational status.

The objective is to demonstrate how using outdated or incorrect oracle data (due to the lack of validation for freshness or operational status) can lead to incorrect contract behavior or exploitation.

Let's start by defining mock functions and data structures that simulate oracle behavior, including TWAP and Chainlink price feeds. We will then fuzz test these by inputting a range of values, including extreme or outdated prices, to see if the contract logic can be broken or misled.
Mock Setup

    Oracle Mock: Simulate TWAP and Chainlink oracle price feeds with both current and outdated prices.
    Price Comparison: Implement the logic to compare TWAP and Chainlink prices similar to _validateAnswer.
    Fuzz Testing: Vary input prices to represent fresh, outdated, and incorrect data scenarios.

Python Simulation

We'll implement the mock oracles, simulate fetching prices from them, and compare the differences without checking for data freshness. Through this simulation, we aim to identify potential vulnerabilities or incorrect behavior when the contract faces outdated or manipulated data.

The fuzz testing, conducted over 1000 iterations, resulted in 1000 instances where a significant deviation was detected between the TWAP and Chainlink prices. This implies that in every single test, the simulated contract logic found a difference greater than the allowed threshold (5% in our simplified scenario), without any checks for the freshness or operational status of the data.
Implications

This result demonstrates a critical vulnerability when relying on oracle data without validating its freshness or operational status:

    Risk of Outdated or Incorrect Data: The simulation shows that if oracle data is outdated, manipulated, or simply incorrect due to operational issues, the contract logic can be easily misled. This leads to incorrect evaluations or actions based on the data.
    Potential for Exploitation: Malicious actors could exploit this vulnerability by manipulating one of the data sources or taking advantage of stale data to trigger actions in their favor.
    Importance of Data Validation: The absence of checks for data freshness or operational status can expose the protocol to significant risks, highlighting the need for such validations to ensure data integrity and contract reliability.

Conclusion

The fuzz test clearly illustrates the vulnerability in relying on oracle data without implementing explicit checks for its freshness or operational status. It underscores the necessity of incorporating safeguards against outdated or incorrect oracle data to maintain the security and effectiveness of smart contracts in real-world scenarios.