S Price: $0.411583 (-0.08%)

Contract

0x44bd4BfD2a361EAa7D905935Cc59D8849639C77D

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

1 Internal Transaction found.

Latest 1 internal transaction

Parent Transaction Hash Block From To
42340702025-01-17 5:57:3622 days ago1737093456  Contract Creation0 S
Loading...
Loading

Minimal Proxy Contract for 0x9b2a312858043b918959f9a21ca89ec666a8afd3

Contract Name:
ChainlinkV3Oracle

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 12 : ChainlinkV3Oracle.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;

import {Initializable} from  "openzeppelin5-upgradeable/proxy/utils/Initializable.sol";
import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol";
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";

import {OracleNormalization} from "../lib/OracleNormalization.sol";
import {ChainlinkV3OracleConfig} from "./ChainlinkV3OracleConfig.sol";
import {IChainlinkV3Oracle} from "../interfaces/IChainlinkV3Oracle.sol";

contract ChainlinkV3Oracle is IChainlinkV3Oracle, ISiloOracle, Initializable {
    ChainlinkV3OracleConfig public oracleConfig;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /// @notice validation of config is checked in factory, therefore you should not deploy and initialize directly
    /// use factory always.
    function initialize(ChainlinkV3OracleConfig _configAddress) external virtual initializer {
        oracleConfig = _configAddress;
        emit ChainlinkV3ConfigDeployed(_configAddress);
    }

    /// @inheritdoc ISiloOracle
    function quote(uint256 _baseAmount, address _baseToken) external view virtual returns (uint256 quoteAmount) {
        ChainlinkV3Config memory config = oracleConfig.getConfig();

        if (_baseToken != address(config.baseToken)) revert AssetNotSupported();
        if (_baseAmount > type(uint128).max) revert BaseAmountOverflow();

        (bool success, uint256 price) = _getAggregatorPrice(config.primaryAggregator, config.primaryHeartbeat);
        if (!success) revert InvalidPrice();

        if (!config.convertToQuote) {
            quoteAmount = OracleNormalization.normalizePrice(
                _baseAmount, price, config.normalizationDivider, config.normalizationMultiplier
            );

            if (quoteAmount == 0) revert ZeroQuote();
            return quoteAmount;
        }

        (
            bool secondSuccess,
            uint256 secondPrice
        ) = _getAggregatorPrice(config.secondaryAggregator, config.secondaryHeartbeat);

        if (!secondSuccess) revert InvalidSecondPrice();

        quoteAmount = OracleNormalization.normalizePrices(
            _baseAmount,
            price,
            secondPrice,
            config.normalizationDivider,
            config.normalizationMultiplier
        );

        if (quoteAmount == 0) revert ZeroQuote();
        return quoteAmount;
    }

    /// @dev Returns price directly from aggregator, this method is mostly for debug purposes
    function getAggregatorPrice(bool _primary) external view virtual returns (bool success, uint256 price) {
        IChainlinkV3Oracle.ChainlinkV3Config memory config = oracleConfig.getConfig();

        return _primary
            ? _getAggregatorPrice(config.primaryAggregator, config.primaryHeartbeat)
            : _getAggregatorPrice(config.secondaryAggregator, config.secondaryHeartbeat);
    }

    /// @inheritdoc ISiloOracle
    function quoteToken() external view virtual returns (address) {
        IChainlinkV3Oracle.ChainlinkV3Config memory config = oracleConfig.getConfig();
        return address(config.quoteToken);
    }

    function beforeQuote(address) external pure virtual override {
        // nothing to execute
    }

    function _getAggregatorPrice(AggregatorV3Interface _aggregator, uint256 _heartbeat)
        internal
        view
        virtual
        returns (bool success, uint256 price)
    {
        (
            /*uint80 roundID*/,
            int256 aggregatorPrice,
            /*uint256 startedAt*/,
            uint256 priceTimestamp,
            /*uint80 answeredInRound*/
        ) = _aggregator.latestRoundData();

        // price must be updated at least once every _heartbeat, otherwise something is wrong
        uint256 oldestAcceptedPriceTimestamp;
        // block.timestamp is more than HEARTBEAT, so we can not underflow
        unchecked { oldestAcceptedPriceTimestamp = block.timestamp - _heartbeat; }

        if (aggregatorPrice > 0 && priceTimestamp > oldestAcceptedPriceTimestamp) {
            return (true, uint256(aggregatorPrice));
        }

        return (false, 0);
    }
}

File 2 of 12 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

File 3 of 12 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 4 of 12 : AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(
    uint80 _roundId
  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

  function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

File 5 of 12 : ISiloOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

interface ISiloOracle {
    /// @notice Hook function to call before `quote` function reads price
    /// @dev This hook function can be used to change state right before the price is read. For example it can be used
    ///      for curve read only reentrancy protection. In majority of implementations this will be an empty function.
    ///      WARNING: reverts are propagated to Silo so if `beforeQuote` reverts, Silo reverts as well.
    /// @param _baseToken Address of priced token
    function beforeQuote(address _baseToken) external;

    /// @return quoteAmount Returns quote price for _baseAmount of _baseToken
    /// @param _baseAmount Amount of priced token
    /// @param _baseToken Address of priced token
    function quote(uint256 _baseAmount, address _baseToken) external view returns (uint256 quoteAmount);

    /// @return address of token in which quote (price) is denominated
    function quoteToken() external view returns (address);
}

File 6 of 12 : OracleNormalization.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;

import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {TokenHelper} from "silo-core/contracts/lib/TokenHelper.sol";

/// @notice please read carefully unchecked comments, there are some requirements tht must be met in order to not
/// over/under flow
/// @dev Rounding error policy.
/// We're always rounding down by using build-in solidity way for division.
///
/// During normalization we're executing division by `_normalizationDivider` (unless there is multiplicator)
/// and `_secondPrice` (in case second price exist). You can expect rounding errors to be in exclusive range of (0, 1)
/// when doing division. What does it means? This means, that you can be short by up to 1 wei on result.
/// eg. when normalising 12_345 (value with 3 decimals) to 2 decimals representation you lose last digit and end result
/// will be 12_34.
/// What are consequences for protocol?
/// Eg. if 987 of tokens A is worth 12.34 tokens B (after normalization), by losing 0.005 we made tokens A worth a bit
/// more than they really are. If we would round up, then tokens A would be a bit less expensive.
/// Keep in mind we are talking tiny values. There is no argument that can tell which approach is correct.
/// Considering that prices themselves are changing constantly (if you think about it, they are just random numbers
/// close to previous value) and even TWAP price can be manipulated up to some level, if we compare this to rounding
/// error, the rounding error has no meaning at all.
/// Most important part is: how are we using prices in Silo and how rounding error affects the system?
/// We're using prices to calculate LTV. We're deciding how much of token you can borrow but once you borrow you need to
/// repay that amount (plus interest). Price of the token has no influence on how much you repay.
/// Price change by 1 wei can also trigger liquidation, but it will be like switching from eg. 9,99999999999% => 10%.
/// Summing up, rounding error can affect:
/// - max amount of tokens one can borrow
/// - when liquidation happen
/// nn both cases we are talking about 1 wei of difference and this really does not matter to the protocol.
/// It cannot make user to repay less than he borrow and it cannot affect any other operations like deposit,
/// withdraw in a way, that you get less/more tokens.
/// That said, choosing rounding policy is arbitrary decision and our decision is to use default rounding down.
library OracleNormalization {
    error Overflow();

    /// @notice if you call normalizePrice directly you can create overflow
    /// @param _baseAmount amount of base token (can not be higher than uint128!)
    /// @param _assetPrice price returned by oracle (can not be higher than uint128!)
    /// @param _normalizationDivider constant that allows to translate output price to expected decimals
    /// @param _normalizationMultiplier constant that allows to translate output price to expected decimals
    /// @return assetPrice uint256 18 decimals price
    function normalizePrice(
        uint256 _baseAmount,
        uint256 _assetPrice,
        uint256 _normalizationDivider,
        uint256 _normalizationMultiplier
    )
        internal
        pure
        returns (uint256 assetPrice)
    {
        if (_normalizationMultiplier == 0) {
            // `_baseAmount * _assetPrice` is safe because we multiply uint128 * uint128
            // - _baseAmount is checked in `_quote`
            // - _assetPrice is uint128
            // div is safe
            unchecked { return _baseAmount * _assetPrice / _normalizationDivider; }
        }

        uint256 mul;
        // this is save, check explanation above
        unchecked { mul = _baseAmount * _assetPrice; }

        return mul * _normalizationMultiplier;
    }

    /// @notice if you call normalizePrice directly you can create overflow
    /// @param _baseAmount amount of base token (can not be higher than uint128!)
    /// @param _assetPrice price returned by oracle (can not be higher than uint128!)
    /// @param _secondPrice price of quote token denominated in same token as _assetPrice
    /// (can not be higher than uint128!)
    /// @param _normalizationDivider constant that allows to translate output price to expected decimals
    /// @param _normalizationMultiplier constant that allows to translate output price to expected decimals
    /// @return assetPrice uint256 18 decimals price
    function normalizePrices(
        uint256 _baseAmount,
        uint256 _assetPrice,
        uint256 _secondPrice,
        uint256 _normalizationDivider,
        uint256 _normalizationMultiplier
    )
        internal
        pure
        returns (uint256 assetPrice)
    {
        if (_normalizationMultiplier == 0) {
            // `_baseAmount * _assetPrice` is safe because we multiply uint128 * uint128
            // - _baseAmount is checked in `_quote`, that checks covers `*1e8`, so we sure it is up to uint128
            // - _assetPrice is uint128
            // however if you call normalizePrice directly (because it is open method) you can create overflow
            // div is safe
            unchecked {
                return _baseAmount * _assetPrice / _normalizationDivider / _secondPrice;
            }
        }

        uint256 mul;
        // this is save, check explanation above
        unchecked { mul = _baseAmount * _assetPrice; }

        mul = mul * _normalizationMultiplier;

        // div is safe
        unchecked { return mul / _secondPrice; }
    }
}

File 7 of 12 : ChainlinkV3OracleConfig.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;

import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol";
import {ISiloOracle} from "silo-core/contracts/interfaces/ISiloOracle.sol";
import {IChainlinkV3Oracle} from "../interfaces/IChainlinkV3Oracle.sol";
import {Layer1OracleConfig} from "../_common/Layer1OracleConfig.sol";

contract ChainlinkV3OracleConfig is Layer1OracleConfig {
    /// @dev Chainlink aggregator
    AggregatorV3Interface internal immutable _AGGREGATOR; // solhint-disable-line var-name-mixedcase

    /// @dev secondary Chainlink aggregator to convert price to quote
    AggregatorV3Interface internal immutable _SECONDARY_AGGREGATOR; // solhint-disable-line var-name-mixedcase

    /// @dev Threshold used to determine if the price returned by the _SECONDARY_AGGREGATOR is valid
    uint256 internal immutable _SECONDARY_HEARTBEAT; // solhint-disable-line var-name-mixedcase

    /// @dev this can be set to true to convert primary price into price denominated in quote
    /// assuming that both AGGREGATORS providing price in the same token
    bool internal immutable _CONVERT_TO_QUOTE; // solhint-disable-line var-name-mixedcase

    /// @dev all verification should be done by factory
    constructor(IChainlinkV3Oracle.ChainlinkV3DeploymentConfig memory _config)
        Layer1OracleConfig(
            _config.baseToken,
            _config.quoteToken,
            _config.primaryHeartbeat,
            _config.normalizationDivider,
            _config.normalizationMultiplier
        )
    {
        _AGGREGATOR = _config.primaryAggregator;
        _SECONDARY_AGGREGATOR = _config.secondaryAggregator;
        _SECONDARY_HEARTBEAT = _config.secondaryHeartbeat;
        _CONVERT_TO_QUOTE = address(_config.secondaryAggregator) != address(0);
    }

    function getConfig() external view virtual returns (IChainlinkV3Oracle.ChainlinkV3Config memory config) {
        config.primaryAggregator = _AGGREGATOR;
        config.secondaryAggregator = _SECONDARY_AGGREGATOR;
        config.primaryHeartbeat = _HEARTBEAT;
        config.secondaryHeartbeat = _SECONDARY_HEARTBEAT;
        config.normalizationDivider = _DECIMALS_NORMALIZATION_DIVIDER;
        config.normalizationMultiplier = _DECIMALS_NORMALIZATION_MULTIPLIER;
        config.baseToken = _BASE_TOKEN;
        config.quoteToken = _QUOTE_TOKEN;
        config.convertToQuote = _CONVERT_TO_QUOTE;
    }
}

File 8 of 12 : IChainlinkV3Oracle.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";
import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol";
import {ChainlinkV3OracleConfig} from "../chainlinkV3/ChainlinkV3OracleConfig.sol";

interface IChainlinkV3Oracle {
    /// @dev config based on which new oracle will be deployed
    /// @notice there is no way to check if aggregators match tokens, so it is users job to verify config.
    /// @param primaryAggregator used to read price from chainlink, if it can not provide price in quote token,
    /// then you have to setup secondary one that will do the job
    /// @param secondaryAggregator if set, it is used translate primary price into quote price eg:
    /// primary price is ABC/USD and secondary is ETH/USD, then result will be price in ABC/ETH
    /// @param baseToken base token address, it must have decimals() method available
    /// @param quoteToken quote toke address, it must have decimals() method available
    /// @param primaryHeartbeat heartbeat of primary price
    /// @param secondaryHeartbeat heartbeat of secondary price
    /// @param normalizationDivider divider that will be used in oracle to normalize price
    /// @param normalizationMultiplier multiplier that will be used in oracle to normalize price
    struct ChainlinkV3DeploymentConfig {
        IERC20Metadata baseToken;
        IERC20Metadata quoteToken;
        AggregatorV3Interface primaryAggregator;
        uint32 primaryHeartbeat;
        AggregatorV3Interface secondaryAggregator;
        uint32 secondaryHeartbeat;
        uint256 normalizationDivider;
        uint256 normalizationMultiplier;
    }

    /// @dev config based on which new oracle will be deployed
    /// @notice there is no way to check if aggregators match tokens, so it is users job to verify config.
    /// @param primaryAggregator used to read price from chainlink, if it can not provide price in quote token,
    /// then you have to setup secondary one that will do the job
    /// @param secondaryAggregator if set, it is used translate primary price into quote price eg:
    /// primary price is ABC/USD and secondary is ETH/USD, then result will be price in ABC/ETH
    /// @param baseToken base token address, it must have decimals() method available
    /// @param quoteToken quote toke address, it must have decimals() method available
    /// @param primaryHeartbeat heartbeat of primary price
    /// @param secondaryHeartbeat heartbeat of secondary price
    struct ChainlinkV3Config {
        AggregatorV3Interface primaryAggregator;
        AggregatorV3Interface secondaryAggregator;
        uint256 primaryHeartbeat;
        uint256 secondaryHeartbeat;
        uint256 normalizationDivider;
        uint256 normalizationMultiplier;
        IERC20Metadata baseToken;
        IERC20Metadata quoteToken;
        bool convertToQuote;
    }

    event ChainlinkV3ConfigDeployed(ChainlinkV3OracleConfig configAddress);

    event NewAggregator(address indexed asset, AggregatorV3Interface indexed aggregator, bool convertToQuote);
    event NewHeartbeat(address indexed asset, uint256 heartbeat);
    event NewQuoteAggregatorHeartbeat(uint256 heartbeat);
    event AggregatorDisabled(address indexed asset, AggregatorV3Interface indexed aggregator);

    error AddressZero();
    error InvalidPrice();
    error ZeroQuote();
    error InvalidSecondPrice();
    error BaseAmountOverflow();
    error TokensAreTheSame();
    error AggregatorsAreTheSame();

    error QuoteTokenNotMatchEth();
    error InvalidEthAggregatorDecimals();
    error InvalidHeartbeat();
    error InvalidEthHeartbeat();

    error AssetNotSupported();
    error HugeDivider();
    error HugeMultiplier();
    error MultiplierAndDividerZero();
}

File 9 of 12 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

File 10 of 12 : TokenHelper.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";

import {IsContract} from "./IsContract.sol";

library TokenHelper {
    uint256 private constant _BYTES32_SIZE = 32;

    error TokenIsNotAContract();

    function assertAndGetDecimals(address _token) internal view returns (uint256) {
        (bool hasMetadata, bytes memory data) =
            _tokenMetadataCall(_token, abi.encodeCall(IERC20Metadata.decimals, ()));

        // decimals() is optional in the ERC20 standard, so if metadata is not accessible
        // we assume there are no decimals and use 0.
        if (!hasMetadata) {
            return 0;
        }

        return abi.decode(data, (uint8));
    }

    /// @dev Returns the symbol for the provided ERC20 token.
    /// An empty string is returned if the call to the token didn't succeed.
    /// @param _token address of the token to get the symbol for
    /// @return assetSymbol the token symbol
    function symbol(address _token) internal view returns (string memory assetSymbol) {
        (bool hasMetadata, bytes memory data) =
            _tokenMetadataCall(_token, abi.encodeCall(IERC20Metadata.symbol, ()));

        if (!hasMetadata || data.length == 0) {
            return "?";
        } else if (data.length == _BYTES32_SIZE) {
            return string(removeZeros(data));
        } else {
            return abi.decode(data, (string));
        }
    }

    /// @dev Removes bytes with value equal to 0 from the provided byte array.
    /// @param _data byte array from which to remove zeroes
    /// @return result byte array with zeroes removed
    function removeZeros(bytes memory _data) internal pure returns (bytes memory result) {
        uint256 n = _data.length;

        for (uint256 i; i < n; i++) {
            if (_data[i] == 0) continue;

            result = abi.encodePacked(result, _data[i]);
        }
    }

    /// @dev Performs a staticcall to the token to get its metadata (symbol, decimals, name)
    function _tokenMetadataCall(address _token, bytes memory _data) private view returns (bool, bytes memory) {
        // We need to do this before the call, otherwise the call will succeed even for EOAs
        require(IsContract.isContract(_token), TokenIsNotAContract());

        (bool success, bytes memory result) = _token.staticcall(_data);

        // If the call reverted we assume the token doesn't follow the metadata extension
        if (!success) {
            return (false, "");
        }

        return (true, result);
    }
}

File 11 of 12 : Layer1OracleConfig.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {IERC20Metadata} from "openzeppelin5/token/ERC20/extensions/IERC20Metadata.sol";

/// @notice to keep config contract size low (this is the one that will be deployed each time)
/// factory contract take over verification. You should not deploy or use config that was not created by factory.
/// @dev This is common config for Layer1 oracles
abstract contract Layer1OracleConfig {
    /// @dev price must be updated at least once every `_HEARTBEAT` seconds, otherwise something is wrong
    uint256 internal immutable _HEARTBEAT; // solhint-disable-line var-name-mixedcase

    /// @dev constant used for normalising price
    uint256 internal immutable _DECIMALS_NORMALIZATION_DIVIDER; // solhint-disable-line var-name-mixedcase

    /// @dev constant used for normalising price
    uint256 internal immutable _DECIMALS_NORMALIZATION_MULTIPLIER; // solhint-disable-line var-name-mixedcase

    IERC20Metadata internal immutable _BASE_TOKEN; // solhint-disable-line var-name-mixedcase
    IERC20Metadata internal immutable _QUOTE_TOKEN; // solhint-disable-line var-name-mixedcase

    /// @dev all verification should be done by factory
    constructor(
        IERC20Metadata _baseToken,
        IERC20Metadata _quoteToken,
        uint256 _heartbeat,
        uint256 _normalizationDivider,
        uint256 _normalizationMultiplier
    ) {
        _DECIMALS_NORMALIZATION_DIVIDER = _normalizationDivider;
        _DECIMALS_NORMALIZATION_MULTIPLIER = _normalizationMultiplier;

        _BASE_TOKEN = _baseToken;
        _QUOTE_TOKEN = _quoteToken;

        _HEARTBEAT = _heartbeat;
    }
}

File 12 of 12 : IsContract.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

library IsContract {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address _account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return _account.code.length > 0;
    }
}

Settings
{
  "remappings": [
    "forge-std/=gitmodules/forge-std/src/",
    "silo-foundry-utils/=gitmodules/silo-foundry-utils/contracts/",
    "properties/=gitmodules/crytic/properties/contracts/",
    "silo-core/=silo-core/",
    "silo-oracles/=silo-oracles/",
    "silo-vaults/=silo-vaults/",
    "ve-silo/=ve-silo/",
    "@openzeppelin/=gitmodules/openzeppelin-contracts-5/contracts/",
    "morpho-blue/=gitmodules/morpho-blue/src/",
    "openzeppelin5/=gitmodules/openzeppelin-contracts-5/contracts/",
    "openzeppelin5-upgradeable/=gitmodules/openzeppelin-contracts-upgradeable-5/contracts/",
    "chainlink/=gitmodules/chainlink/contracts/src/",
    "chainlink-ccip/=gitmodules/chainlink-ccip/contracts/src/",
    "uniswap/=gitmodules/uniswap/",
    "@uniswap/v3-core/=gitmodules/uniswap/v3-core/",
    "balancer-labs/v2-solidity-utils/=external/balancer-v2-monorepo/pkg/solidity-utils/contracts/",
    "balancer-labs/v2-interfaces/=external/balancer-v2-monorepo/pkg/interfaces/contracts/",
    "balancer-labs/v2-liquidity-mining/=external/balancer-v2-monorepo/pkg/liquidity-mining/contracts/",
    "pyth-sdk-solidity/=gitmodules/pyth-sdk-solidity/target_chains/ethereum/sdk/solidity/",
    "@balancer-labs/=node_modules/@balancer-labs/",
    "@ensdomains/=node_modules/@ensdomains/",
    "@openzeppelin/contracts-upgradeable/=gitmodules/openzeppelin-contracts-upgradeable-5/contracts/",
    "@openzeppelin/contracts/=gitmodules/openzeppelin-contracts-5/contracts/",
    "@solidity-parser/=node_modules/@solidity-parser/",
    "ERC4626/=gitmodules/crytic/properties/lib/ERC4626/contracts/",
    "createx/=gitmodules/pyth-sdk-solidity/lazer/contracts/evm/lib/createx/src/",
    "crytic/=gitmodules/crytic/",
    "ds-test/=gitmodules/openzeppelin-contracts-5/lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=gitmodules/openzeppelin-contracts-5/lib/erc4626-tests/",
    "halmos-cheatcodes/=gitmodules/morpho-blue/lib/halmos-cheatcodes/src/",
    "hardhat/=node_modules/hardhat/",
    "openzeppelin-contracts-5/=gitmodules/openzeppelin-contracts-5/",
    "openzeppelin-contracts-upgradeable-5/=gitmodules/openzeppelin-contracts-upgradeable-5/",
    "openzeppelin-contracts-upgradeable/=gitmodules/pyth-sdk-solidity/lazer/contracts/evm/lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=gitmodules/openzeppelin-contracts-upgradeable-5/lib/openzeppelin-contracts/",
    "prettier-plugin-solidity/=node_modules/prettier-plugin-solidity/",
    "proposals/=node_modules/proposals/",
    "solady/=gitmodules/pyth-sdk-solidity/lazer/contracts/evm/lib/createx/lib/solady/",
    "solmate/=gitmodules/crytic/properties/lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {}
}

Contract ABI

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressZero","type":"error"},{"inputs":[],"name":"AggregatorsAreTheSame","type":"error"},{"inputs":[],"name":"AssetNotSupported","type":"error"},{"inputs":[],"name":"BaseAmountOverflow","type":"error"},{"inputs":[],"name":"HugeDivider","type":"error"},{"inputs":[],"name":"HugeMultiplier","type":"error"},{"inputs":[],"name":"InvalidEthAggregatorDecimals","type":"error"},{"inputs":[],"name":"InvalidEthHeartbeat","type":"error"},{"inputs":[],"name":"InvalidHeartbeat","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidPrice","type":"error"},{"inputs":[],"name":"InvalidSecondPrice","type":"error"},{"inputs":[],"name":"MultiplierAndDividerZero","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"QuoteTokenNotMatchEth","type":"error"},{"inputs":[],"name":"TokensAreTheSame","type":"error"},{"inputs":[],"name":"ZeroQuote","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"contract AggregatorV3Interface","name":"aggregator","type":"address"}],"name":"AggregatorDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract ChainlinkV3OracleConfig","name":"configAddress","type":"address"}],"name":"ChainlinkV3ConfigDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":true,"internalType":"contract AggregatorV3Interface","name":"aggregator","type":"address"},{"indexed":false,"internalType":"bool","name":"convertToQuote","type":"bool"}],"name":"NewAggregator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"heartbeat","type":"uint256"}],"name":"NewHeartbeat","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"heartbeat","type":"uint256"}],"name":"NewQuoteAggregatorHeartbeat","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"beforeQuote","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"_primary","type":"bool"}],"name":"getAggregatorPrice","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"uint256","name":"price","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ChainlinkV3OracleConfig","name":"_configAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"oracleConfig","outputs":[{"internalType":"contract ChainlinkV3OracleConfig","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_baseAmount","type":"uint256"},{"internalType":"address","name":"_baseToken","type":"address"}],"name":"quote","outputs":[{"internalType":"uint256","name":"quoteAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"quoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

Block Transaction Gas Used Reward
view all blocks produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.