Skip to main content
PulseScanner.io

Address

0x16edbb3bbc0dcef198f042e8d3a434adc5cf4b1a
Current Holdings
$0.00
TXs sent
not counted
First Active
2025-09-23
block 24,580,683
Last Active
361 days ago
block 24,580,713
Funded By
not identified

Net worth historyi

No net-worth snapshots recorded yet
partial matchPriceOracleV3solc 0.8.20+commit.a1b79de6runtime partial · creation partial
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

import {AddressIsNotContractException, IncorrectTokenContractException, PriceFeedDoesNotExistException} from "../interfaces/IExceptions.sol";
import {IPriceOracleV3, PriceFeedParams} from "../interfaces/IPriceOracleV3.sol";

import {ACLNonReentrantTrait} from "../traits/ACLNonReentrantTrait.sol";
import {PriceFeedValidationTrait} from "../traits/PriceFeedValidationTrait.sol";

/// @title Price oracle V3
/// @notice Acts as router that dispatches calls to corresponding price feeds.
///         Underlying price feeds can be arbitrary, but they must adhere to Chainlink interface,
///         e.g., implement `latestRoundData` and always return answers with 8 decimals.
///         They may also implement their own price checks, in which case they may incidcate it
///         to the price oracle by returning `skipPriceCheck = true`.
/// @notice Price oracle also allows to set a reserve price feed for a token, that can be activated
///         in case the main one becomes stale or starts returning wrong values.
///         One should not expect the reserve price feed to always differ from the main one, although
///         most often that would be the case.
/// @notice Price oracle additionaly provides "safe" conversion functions, which use minimum of main
///         and reserve feed prices (the latter is assumed to be zero if reserve feed is not set).
///         There are also trusted price feeds, for which safe prices are simply main feed prices.
contract PriceOracleV3 is
    ACLNonReentrantTrait,
    PriceFeedValidationTrait,
    IPriceOracleV3
{
    /// @notice Contract version
    uint256 public constant override version = 3_00;

    /// @dev Mapping from token address to price feed parameters
    mapping(address => PriceFeedParams) internal _priceFeedsParams;

    /// @dev Flag to revert transactions when price is zero
    bool revertOnZeroPrice = true;

    /// @notice Constructor
    /// @param addressProvider Address provider contract address
    constructor(
        address addressProvider
    ) ACLNonReentrantTrait(addressProvider) {}

    /// @notice Returns `token`'s price in USD (with 8 decimals) from the currently active price feed
    function getPrice(
        address token
    ) public view override returns (uint256 price) {
        (price, ) = _getPrice(token);
    }

    /// @notice Returns `token`'s safe price in USD (with 8 decimals)
    function getPriceSafe(
        address token
    ) external view override returns (uint256 price) {
        (price, ) = _getPriceSafe(token);
    }

    /// @notice Returns `token`'s price in USD (with 8 decimals) from the specified price feed
    function getPriceRaw(
        address token,
        bool reserve
    ) external view returns (uint256 price) {
        (price, ) = _getPriceRaw(token, reserve);
    }

    /// @notice Converts `amount` of `token` into USD amount (with 8 decimals)
    function convertToUSD(
        uint256 amount,
        address token
    ) external view override returns (uint256) {
        (uint256 price, uint256 scale) = _getPrice(token);
        return (amount * price) / scale;
    }

    /// @notice Converts `amount` of USD (with 8 decimals) into `token` amount
    function convertFromUSD(
        uint256 amount,
        address token
    ) external view override returns (uint256) {
        (uint256 price, uint256 scale) = _getPrice(token);
        return (amount * scale) / price;
    }

    /// @notice Converts `amount` of `tokenFrom` into `tokenTo` amount
    function convert(
        uint256 amount,
        address tokenFrom,
        address tokenTo
    ) external view override returns (uint256) {
        (uint256 priceFrom, uint256 scaleFrom) = _getPrice(tokenFrom);
        (uint256 priceTo, uint256 scaleTo) = _getPrice(tokenTo);
        return (amount * priceFrom * scaleTo) / (priceTo * scaleFrom);
    }

    /// @notice Converts `amount` of `token` into USD amount (with 8 decimals) using safe price
    function safeConvertToUSD(
        uint256 amount,
        address token
    ) external view override returns (uint256) {
        (uint256 price, uint256 scale) = _getPriceSafe(token);
        return (amount * price) / scale;
    }

    /// @notice Returns the price feed for `token` or reverts if price feed is not set
    function priceFeeds(
        address token
    ) external view override returns (address priceFeed) {
        (priceFeed, , , , ) = priceFeedParams(token);
    }

    /// @notice Returns the specified price feed for `token`
    function priceFeedsRaw(
        address token,
        bool reserve
    ) external view override returns (address priceFeed) {
        (priceFeed, , , , , ) = _getPriceFeedParams(
            reserve ? _getTokenReserveKey(token) : token
        );
    }

    /// @notice Returns currently active price feed parameters for `token` or reverts if price feed is not set
    function priceFeedParams(
        address token
    )
        public
        view
        override
        returns (
            address priceFeed,
            uint32 stalenessPeriod,
            bool skipCheck,
            uint8 decimals,
            bool trusted
        )
    {
        bool useReserve;
        (
            priceFeed,
            stalenessPeriod,
            skipCheck,
            decimals,
            useReserve,
            trusted
        ) = _getPriceFeedParams(token);
        if (useReserve) {
            (
                priceFeed,
                stalenessPeriod,
                skipCheck,
                decimals,
                ,
                trusted
            ) = _getPriceFeedParams(_getTokenReserveKey(token));
        }
    }

    // --------- //
    // INTERNALS //
    // --------- //

    /// @dev Returns `token`'s price and scale from the currently active price feed
    function _getPrice(
        address token
    ) internal view returns (uint256 price, uint256 scale) {
        (
            address priceFeed,
            uint32 stalenessPeriod,
            bool skipCheck,
            uint8 decimals,

        ) = priceFeedParams(token);
        return _getPrice(priceFeed, stalenessPeriod, skipCheck, decimals);
    }

    /// @dev Returns `token`'s price and scale from the explicitly specified price feed
    function _getPriceRaw(
        address token,
        bool reserve
    ) internal view returns (uint256 price, uint256 scale) {
        (
            address priceFeed,
            uint32 stalenessPeriod,
            bool skipCheck,
            uint8 decimals,
            ,

        ) = _getPriceFeedParams(reserve ? _getTokenReserveKey(token) : token);
        return _getPrice(priceFeed, stalenessPeriod, skipCheck, decimals);
    }

    /// @dev Returns `token`'s safe price and scale
    function _getPriceSafe(
        address token
    ) internal view returns (uint256 price, uint256 scale) {
        (
            address priceFeed,
            uint32 stalenessPeriod,
            bool skipCheck,
            uint8 decimals,
            ,
            bool trusted
        ) = _getPriceFeedParams(token);
        (price, scale) = _getPrice(
            priceFeed,
            stalenessPeriod,
            skipCheck,
            decimals
        );

        if (!trusted) {
            if (
                _priceFeedsParams[_getTokenReserveKey(token)].priceFeed ==
                address(0)
            ) return (0, scale);
            (uint256 reservePrice, ) = _getPriceRaw(token, true);
            price = Math.min(price, reservePrice);
        }
    }

    /// @dev Returns price feed answer and scale, optionally performs sanity and staleness checks
    function _getPrice(
        address priceFeed,
        uint32 stalenessPeriod,
        bool skipCheck,
        uint8 decimals
    ) internal view returns (uint256 price, uint256 scale) {
        int256 answer = _getValidatedPrice(priceFeed);

        price = uint256(answer);

        // Check for zero price if enabled
        if (revertOnZeroPrice && price == 0) {
            revert PriceFeedDoesNotExistException();
        }

        // 1 <= decimals <= 18, so the operation is safe
        unchecked {
            scale = 10 ** decimals;
        }
    }

    /// @dev Efficiently loads `token`'s price feed parameters from storage
    function _getPriceFeedParams(
        address token
    )
        internal
        view
        returns (
            address priceFeed,
            uint32 stalenessPeriod,
            bool skipCheck,
            uint8 decimals,
            bool useReserve,
            bool trusted
        )
    {
        PriceFeedParams storage params = _priceFeedsParams[token];
        assembly {
            let data := sload(params.slot)
            priceFeed := data
            stalenessPeriod := shr(160, data)
            skipCheck := and(shr(192, data), 0x01)
            decimals := shr(200, data)
            useReserve := and(shr(208, data), 0x01)
            trusted := and(shr(216, data), 0x01)
        }

        if (priceFeed == address(0)) revert PriceFeedDoesNotExistException();
    }

    /// @dev Returns key that is used to store `token`'s reserve feed in `_priceFeedParams`
    function _getTokenReserveKey(
        address token
    ) internal pure returns (address key) {
        // address(uint160(uint256(keccak256(abi.encodePacked("RESERVE", token)))))
        assembly {
            mstore(0x0, or("RESERVE", shl(0x28, token)))
            key := keccak256(0x0, 0x1b)
        }
    }

    // ------------- //
    // CONFIGURATION //
    // ------------- //

    /// @notice Sets whether to revert transactions when price is zero
    /// @param _revertOnZeroPrice True to revert on zero prices, false to allow
    function setRevertOnZeroPrice(
        bool _revertOnZeroPrice
    ) external configuratorOnly {
        revertOnZeroPrice = _revertOnZeroPrice;
        emit RevertOnZeroPriceSet(_revertOnZeroPrice);
    }

    /// @notice Emitted when revert on zero price setting is changed
    event RevertOnZeroPriceSet(bool revertOnZeroPrice);

    /// @notice Sets price feed for a given token
    function setPriceFeed(
        address token,
        address priceFeed
    )
        external
        override
        nonZeroAddress(token)
        nonZeroAddress(priceFeed)
        configuratorOnly
    {
        uint8 decimals = _priceFeedsParams[token].decimals;
        decimals = decimals == 0 ? _validateToken(token) : decimals;

        bool skipCheck = true;
        _priceFeedsParams[token] = PriceFeedParams({
            priceFeed: priceFeed,
            stalenessPeriod: 0,
            skipCheck: skipCheck,
            decimals: decimals,
            useReserve: false,
            trusted: true
        });
        emit SetPriceFeed(token, priceFeed, 0, skipCheck, true);
    }

    /// @notice Sets reserve price feed for a given token
    /// @dev Main price feed for the token must already be set
    function setReservePriceFeed(
        address token,
        address priceFeed,
        uint32 stalenessPeriod
    )
        external
        override
        nonZeroAddress(token)
        nonZeroAddress(priceFeed)
        configuratorOnly
    {
        uint8 decimals = _priceFeedsParams[token].decimals;
        if (decimals == 0) revert PriceFeedDoesNotExistException();

        bool skipCheck = _validatePriceFeed(priceFeed, stalenessPeriod);
        _priceFeedsParams[_getTokenReserveKey(token)] = PriceFeedParams({
            priceFeed: priceFeed,
            stalenessPeriod: stalenessPeriod,
            skipCheck: skipCheck,
            decimals: decimals,
            useReserve: false,
            trusted: false
        });
        emit SetReservePriceFeed(token, priceFeed, stalenessPeriod, skipCheck);
    }

    /// @notice Sets `token`'s reserve price feed status to `active`
    /// @dev Reserve price feed for the token must already be set
    function setReservePriceFeedStatus(
        address token,
        bool active
    ) external override controllerOnly {
        if (
            _priceFeedsParams[_getTokenReserveKey(token)].priceFeed ==
            address(0)
        ) {
            revert PriceFeedDoesNotExistException();
        }

        if (_priceFeedsParams[token].useReserve != active) {
            _priceFeedsParams[token].useReserve = active;
            emit SetReservePriceFeedStatus(token, active);
        }
    }

    /// @dev Validates that `token` is a contract that returns `decimals` within allowed range
    function _validateToken(
        address token
    ) internal view returns (uint8 decimals) {
        // if (!Address.isContract(token)) revert AddressIsNotContractException(token);
        try ERC20(token).decimals() returns (uint8 _decimals) {
            if (_decimals == 0 || _decimals > 18) {
                revert IncorrectTokenContractException();
            }
            decimals = _decimals;
        } catch {
            revert IncorrectTokenContractException();
        }
    }
}