EOMultiFeedAdapter

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/EOMultiFeedAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { IEOMultiFeedAdapter } from "./interfaces/IEOMultiFeedAdapter.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { FeedUtils } from "./libraries/FeedUtils.sol";
import { EOBase } from "./EOBase.sol";

/**
 * @title EOMultiFeedAdapter
 * @author EO
 * @notice A contract that allows to create a price feed by combining multiple base and quote feeds.
 * @dev supports base and quote feeds of type MinimalAggregatorV3Interface, IERC4626, ICustomAdapter
 */
contract EOMultiFeedAdapter is IEOMultiFeedAdapter, EOBase, Initializable {
    using FeedUtils for Feed;

    /**
     * @notice The maximum number of base feeds.
     */
    uint256 public constant MAX_BASE_FEEDS = 4;

    /**
     * @notice The maximum number of quote feeds.
     */
    uint256 public constant MAX_QUOTE_FEEDS = 2;

    /**
     * @notice The maximum number of decimals for the price feed.
     */
    uint256 public constant MAX_DECIMALS = 64;

    /**
     * @notice The rate volatility buffer.
     */
    int256 public constant RATE_VOLATILITY_BUFFER = 1_000_000;

    /**
     * @notice The numerator multiplier of the price feed.
     */
    int256 public numeratorMultiplier;

    /**
     * @notice The denominator multiplier of the price feed.
     */
    int256 public denominatorMultiplier;

    /**
     * @notice The decimals precision of the price feed.
     */
    uint8 public decimals;

    /**
     * @notice The description of the price feed.
     */
    string public description;

    /**
     * @notice The base feeds used to calculate the rate of the price feed.
     */
    Feed[] private _baseFeeds;

    /**
     * @notice The quote feeds used to calculate the rate of the price feed.
     */
    Feed[] private _quoteFeeds;

    /**
     * @notice The gap for the upgradeable contract.
     */
    uint256[50] private _gap;

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

    /**
     * @notice Initializes the price feed adapter.
     * @dev Initialization is considered safe if the following conditions hold:
     *  - The total decimals after adjustment do not exceed MAX_DECIMALS.
     *  - The product of current base feed rates multiplied by numeratorMultiplier is well below
     *    type(int256).max by a factor of RATE_VOLATILITY_BUFFER, providing headroom for price volatility.
     *
     * These checks ensure:
     *  - The configured feeds and output decimals cannot cause immediate overflow.
     *  - `latestRoundData` remains safe even if prices fluctuate significantly within a reasonable range.
     *
     * Reverts if:
     *  - No feeds are provided.
     *  - The number of feeds exceeds MAX_BASE_FEEDS or MAX_QUOTE_FEEDS.
     *  - Any feed address is invalid.
     *  - The computed decimals exceed MAX_DECIMALS.
     *  - Potential overflow is detected based on current base rates and the safety buffer.
     *
     * @param baseFeeds_ The base feeds for the rate calculation.
     * @param quoteFeeds_ The quote feeds for the rate calculation.
     * @param outputDecimals_ The decimals of the aggregated price feed.
     * @param description_ The human-readable description of the feed.
     */
    // solhint-disable-next-line code-complexity
    function initialize(
        Feed[] memory baseFeeds_,
        Feed[] memory quoteFeeds_,
        uint8 outputDecimals_,
        string memory description_
    )
        external
        initializer
    {
        uint8 baseDecimals = 0;
        uint8 quoteDecimals = 0;
        if (baseFeeds_.length > MAX_BASE_FEEDS) revert MaxBaseFeedsExceeded();
        if (quoteFeeds_.length > MAX_QUOTE_FEEDS) revert MaxQuoteFeedsExceeded();
        if (baseFeeds_.length + quoteFeeds_.length == 0) revert NoFeedsProvided();

        for (uint256 i = 0; i < baseFeeds_.length; i++) {
            if (baseFeeds_[i].feedAddress == address(0)) {
                revert InvalidFeedAddress();
            }
            baseFeeds_[i].verify();
            _baseFeeds.push(baseFeeds_[i]);
            baseDecimals += baseFeeds_[i].getDecimals();
        }

        for (uint256 i = 0; i < quoteFeeds_.length; i++) {
            if (quoteFeeds_[i].feedAddress == address(0)) {
                revert InvalidFeedAddress();
            }
            quoteFeeds_[i].verify();
            _quoteFeeds.push(quoteFeeds_[i]);
            quoteDecimals += quoteFeeds_[i].getDecimals();
        }

        description = description_;
        decimals = outputDecimals_;

        int8 adjustment = int8(outputDecimals_) - int8(baseDecimals) + int8(quoteDecimals);

        if (adjustment >= 0) {
            if (baseDecimals + uint8(adjustment) > MAX_DECIMALS) revert MaxDecimalsExceeded();
            numeratorMultiplier = int256(10 ** uint8(adjustment));
            denominatorMultiplier = 1;
        } else {
            if (baseDecimals > MAX_DECIMALS) revert MaxDecimalsExceeded();
            numeratorMultiplier = 1;
            denominatorMultiplier = int256(10 ** uint8(-adjustment));
        }

        _validateRateSafety(numeratorMultiplier, baseFeeds_);
    }

    /**
     * @notice The function to get the base feeds of the price feed.
     * @return The array of base feeds, where each item is (feedInterface, feedAddress, customAdapterAddress),
     *         where feedInterface (0 = MinimalAggregatorV3Interface, 1 = IERC4626, 2 = CustomAdapterInterface)
     */
    function getBaseFeeds() external view returns (Feed[] memory) {
        return _baseFeeds;
    }

    /**
     * @notice The function to get the base feed by index.
     * @param index The index of the base feed.
     * @return The base feed at the given index, (feedInterface, feedAddress, customAdapterAddress),
     *         where feedInterface (0 = MinimalAggregatorV3Interface, 1 = IERC4626, 2 = CustomAdapterInterface)
     */
    function getBaseFeed(uint256 index) external view returns (Feed memory) {
        return _baseFeeds[index];
    }

    /**
     * @notice The function to get the quote feeds of the price feed.
     * @return The array of quote feeds, where each item is (feedInterface, feedAddress, customAdapterAddress),
     *         where feedInterface (0 = MinimalAggregatorV3Interface, 1 = IERC4626, 2 = CustomAdapterInterface)
     */
    function getQuoteFeeds() external view returns (Feed[] memory) {
        return _quoteFeeds;
    }

    /**
     * @notice The function to get the quote feed by index.
     * @param index The index of the quote feed.
     * @return The quote feed at the given index, (feedInterface, feedAddress, customAdapterAddress),
     *         where feedInterface (0 = MinimalAggregatorV3Interface, 1 = IERC4626, 2 = CustomAdapterInterface)
     */
    function getQuoteFeed(uint256 index) external view returns (Feed memory) {
        return _quoteFeeds[index];
    }

    /**
     * @notice Returns the minimum updated at among the base and quote feeds
     * @dev reflects the data freshness
     * @return The minimum updated at among the base and quote feeds
     */
    function minUpdatedAt() external view override returns (uint256) {
        uint256 baseFeedsLength = _baseFeeds.length;
        uint256 quoteFeedsLength = _quoteFeeds.length;
        uint256 earliestUpdatedAt = block.timestamp;
        for (uint256 i = 0; i < baseFeedsLength; i++) {
            (, uint256 updatedAt) = _baseFeeds[i].getRate();
            if (updatedAt < earliestUpdatedAt) {
                earliestUpdatedAt = updatedAt;
            }
        }
        for (uint256 i = 0; i < quoteFeedsLength; i++) {
            (, uint256 updatedAt) = _quoteFeeds[i].getRate();
            if (updatedAt < earliestUpdatedAt) {
                earliestUpdatedAt = updatedAt;
            }
        }
        return earliestUpdatedAt;
    }

    /**
     * @notice Returns latest round data
     * @dev Returns zero for roundId, startedAt and answeredInRound.
     * @return roundId The round id, returns 0
     * @return answer The answer, returns the final rate of the price feed,
     *  rate = (rate_base1 * ... * rate_baseN * numeratorMultiplier) /
     *          (rate_quote1 * ... * rate_quoteM * denominatorMultiplier)
     * @return startedAt The started at, returns 0
     * @return updatedAt The updated at, returns latest updatedAt among the base and quote feeds
     * @return answeredInRound The answered in round, returns 0
     */
    function latestRoundData() public view override returns (uint80, int256, uint256, uint256, uint80) {
        int256 rate = numeratorMultiplier;
        uint256 baseFeedsLength = _baseFeeds.length;
        // slither-disable-next-line uninitialized-local
        uint256 latestUpdatedAt;
        for (uint256 i = 0; i < baseFeedsLength; i++) {
            (int256 feedRate, uint256 updatedAt) = _baseFeeds[i].getRate();
            // slither-disable-next-line divide-before-multiply
            rate = rate * feedRate;
            if (updatedAt > latestUpdatedAt) {
                latestUpdatedAt = updatedAt;
            }
        }
        uint256 quoteFeedsLength = _quoteFeeds.length;
        for (uint256 i = 0; i < quoteFeedsLength; i++) {
            (int256 feedRate, uint256 updatedAt) = _quoteFeeds[i].getRate();
            // slither-disable-next-line divide-before-multiply
            rate = rate / feedRate;
            if (updatedAt > latestUpdatedAt) {
                latestUpdatedAt = updatedAt;
            }
        }
        rate = rate / denominatorMultiplier;
        return (0, rate, 0, latestUpdatedAt, 0);
    }

    /**
     * @notice Checks if the current base feed rates product can potentially overflow `int256`
     *         when multiplied by `numeratorMultiplier`, with a large volatility buffer.
     * @dev Formula:
     *
     *      Let:
     *        R = product of all base feed rates
     *        N = numeratorMultiplier
     *        B = RATE_VOLATILITY_BUFFER (1e6)
     *        M = type(int256).max ≈ 5.79 × 10^76
     *
     *      We require:
     *        (R * N * B) <= M
     *
     *      This ensures that even if prices increase by a factor of B after initialization,
     *      the multiplication in `latestRoundData` will not overflow.
     *
     *      Example:
     *        - M = 5.7896 × 10^76
     *        - B = 1e6 → Safe bound = M / B ≈ 5.7896 × 10^70
     *        - If current R * N is below 10^70, we have 6 orders of magnitude headroom for volatility.
     * @param numeratorMultiplier_ The numerator multiplier of the price feed.
     * @param baseFeeds_ The base feeds for the rate calculation.
     */
    function _validateRateSafety(int256 numeratorMultiplier_, Feed[] memory baseFeeds_) internal view {
        int256 rate = numeratorMultiplier_;
        uint256 baseFeedsLength = baseFeeds_.length;
        for (uint256 i = 0; i < baseFeedsLength; i++) {
            (int256 feedRate,) = baseFeeds_[i].getRate();
            rate = rate * feedRate;
        }
        // Check against safe bound
        if (rate > type(int256).max / RATE_VOLATILITY_BUFFER) {
            revert PotentialRateOverflow();
        }
    }
}
"
    },
    "src/interfaces/IEOMultiFeedAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

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

/**
 * @title IEOMultiFeedAdapter
 * @author EO
 * @notice Interface for the EOMultiFeedAdapter contract.
 */
interface IEOMultiFeedAdapter is IEOFeedAdapterBase {
    /**
     * @notice The error emitted when the maximum number of base feeds is exceeded.
     */
    error MaxBaseFeedsExceeded();

    /**
     * @notice The error emitted when the maximum number of quote feeds is exceeded.
     */
    error MaxQuoteFeedsExceeded();

    /**
     * @notice The error emitted when the maximum number of decimals is exceeded.
     */
    error MaxDecimalsExceeded();

    /**
     * @notice The error emitted when no feeds are provided.
     */
    error NoFeedsProvided();

    /**
     * @notice The error emitted when the potential rate overflow is detected.
     */
    error PotentialRateOverflow();

    function initialize(
        Feed[] memory baseFeeds_,
        Feed[] memory quoteFeeds_,
        uint8 outputDecimals_,
        string memory description_
    )
        external;

    function getBaseFeeds() external view returns (Feed[] memory);

    function getBaseFeed(uint256 index) external view returns (Feed memory);

    function getQuoteFeeds() external view returns (Feed[] memory);

    function getQuoteFeed(uint256 index) external view returns (Feed memory);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.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 reinitialization) 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 Pointer to storage slot. Allows integrators to override it with a custom storage location.
     *
     * NOTE: Consider following the ERC-7201 formula to derive storage locations.
     */
    function _initializableStorageSlot() internal pure virtual returns (bytes32) {
        return INITIALIZABLE_STORAGE;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        bytes32 slot = _initializableStorageSlot();
        assembly {
            $.slot := slot
        }
    }
}
"
    },
    "src/libraries/FeedUtils.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { InterfaceVerifier } from "./InterfaceVerifier.sol";
import { IEOFeedAdapterBase } from "../interfaces/IEOFeedAdapterBase.sol";
import { MinimalAggregatorV3Interface } from "../interfaces/MinimalAggregatorV3Interface.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IDecimals } from "../interfaces/IDecimals.sol";
import { ICustomAdapter } from "../interfaces/ICustomAdapter.sol";

/**
 * @title FeedUtils
 * @author EO
 * @notice A library for feed verification and rate/decimals retrieval.
 */
library FeedUtils {
    using InterfaceVerifier for address;

    /**
     * @notice The error emitted when the feed interface is not supported.
     * @param feed The address of the feed.
     * @param feedInterface The interface of the feed.
     */
    error FeedInterfaceNotSupported(address feed, IEOFeedAdapterBase.FeedInterface feedInterface);

    /**
     * @notice The error emitted when the feed rate is zero.
     * @param feed The feed to verify.
     */
    error ZeroFeedRate(address feed);

    /**
     * @notice Verifies the feed interface.
     * @dev Reverts if the feed interface is not supported or the feed interface is not implemented.
     * @param feed The feed to verify.
     */
    function verify(IEOFeedAdapterBase.Feed memory feed) internal view {
        if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.MINIMAL_AGGREGATOR_V3_INTERFACE) {
            feed.feedAddress.verifyMinimalAggregatorV3Interface();
        } else if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.IERC4626_VAULT) {
            feed.feedAddress.verifyIERC4626();
        } else if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.CUSTOM_ADAPTER_INTERFACE) {
            feed.feedAddress.verifyICustomAdapter(feed.customAdapterAddress);
        } else {
            revert FeedInterfaceNotSupported(feed.feedAddress, feed.feedInterface);
        }
    }

    /**
     * @notice Retrieves the rate of the feed.
     * @dev Reverts if the feed interface is not supported.
     * @param feed The feed to retrieve the rate from.
     * @return rate The rate of the feed.
     * @return updatedAt The timestamp of the last update, or block.timestamp if it is 0
     */
    function getRate(IEOFeedAdapterBase.Feed memory feed) internal view returns (int256 rate, uint256 updatedAt) {
        if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.MINIMAL_AGGREGATOR_V3_INTERFACE) {
            (, rate,, updatedAt,) = MinimalAggregatorV3Interface(feed.feedAddress).latestRoundData();
        } else if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.IERC4626_VAULT) {
            IERC4626 vault = IERC4626(feed.feedAddress);
            rate = int256(vault.convertToAssets(10 ** vault.decimals()));
            updatedAt = block.timestamp;
        } else if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.CUSTOM_ADAPTER_INTERFACE) {
            rate = ICustomAdapter(feed.customAdapterAddress).getCustomRate(feed.feedAddress);
            updatedAt = block.timestamp;
        } else {
            revert FeedInterfaceNotSupported(feed.feedAddress, feed.feedInterface);
        }
        if (rate <= 0) revert ZeroFeedRate(feed.feedAddress);
    }

    /**
     * @notice Retrieves the decimals of the feed.
     * @dev Reverts if the feed interface is not supported.
     * @param feed The feed to retrieve the decimals from.
     * @return decimals The decimals of the feed.
     */
    function getDecimals(IEOFeedAdapterBase.Feed memory feed) internal view returns (uint8) {
        if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.MINIMAL_AGGREGATOR_V3_INTERFACE) {
            return IDecimals(feed.feedAddress).decimals();
        } else if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.IERC4626_VAULT) {
            return IDecimals(IERC4626(feed.feedAddress).asset()).decimals();
        } else if (feed.feedInterface == IEOFeedAdapterBase.FeedInterface.CUSTOM_ADAPTER_INTERFACE) {
            try ICustomAdapter(feed.customAdapterAddress).getDecimals(feed.feedAddress) returns (uint8 decimals) {
                return decimals;
            } catch {
                return IDecimals(feed.feedAddress).decimals();
            }
        } else {
            revert FeedInterfaceNotSupported(feed.feedAddress, feed.feedInterface);
        }
    }
}
"
    },
    "src/EOBase.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { AggregatorV2V3Interface } from "./interfaces/AggregatorV2V3Interface.sol";

/**
 * @title EOBase
 * @author EO
 * @notice Base contract for EO contracts
 */
abstract contract EOBase is AggregatorV2V3Interface {
    /**
     * @notice Returns the latest answer
     * @return The latest answer
     */
    function latestAnswer() external view returns (int256) {
        (, int256 answer,,,) = latestRoundData();
        return answer;
    }

    /**
     * @notice Returns the latest update timestamp
     * @return The latest update timestamp
     */
    function latestTimestamp() external view returns (uint256) {
        (,,, uint256 updatedAt,) = latestRoundData();
        return updatedAt;
    }

    /**
     * @notice Returns the latest round ID
     * @return The latest round ID
     */
    function latestRound() external view returns (uint256) {
        (uint80 roundId,,,,) = latestRoundData();
        return uint256(roundId);
    }

    /**
     * @notice Returns the answer of the latest round
     * @param roundId The round ID (ignored)
     * @return The answer of the latest round
     */
    function getAnswer(uint256 roundId) external view returns (int256) {
        (, int256 answer,,,) = getRoundData(uint80(roundId));
        return answer;
    }

    /**
     * @notice Returns the update timestamp of the latest round
     * @param roundId The round ID (ignored)
     * @return The update timestamp of the latest round
     */
    function getTimestamp(uint256 roundId) external view returns (uint256) {
        (,,, uint256 updatedAt,) = getRoundData(uint80(roundId));
        return updatedAt;
    }

    /**
     * @notice Returns the minimum updated at, default to updatedAt in latestRoundData
     * @dev This method is valuable for complex feeds, where other feeds rates are involved into computation
     * and since updatedAt in latestRoundData is the latest updatedAt among the feeds, it may not reflect the data
     * freshness, so this method shows the earliest updatedAt among the feeds, and reflects the data freshness
     * @return The minimum updated at among the feeds used to compute the final rate
     */
    function minUpdatedAt() external view virtual returns (uint256) {
        (,,, uint256 updatedAt,) = latestRoundData();
        return updatedAt;
    }

    /**
     * @notice Returns the latest round data
     * @return roundId The latest round ID, optional
     * @return answer The latest answer
     * @return startedAt The timestamp when the round started, optional
     * @return updatedAt The timestamp of the latest update
     * @return answeredInRound The round ID in which the answer was computed, optional
     */
    function latestRoundData()
        public
        view
        virtual
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    /**
     * @notice Returns the latest round data
     * @param roundId The round ID, is ignored
     * @return roundId The round ID of   the latest round data
     * @return answer The answer of the latest round data
     * @return startedAt The started at of the latest round data
     * @return updatedAt The updated at of the latest round data
     * @return answeredInRound The round ID in which the answer was computed of the latest round data
     */
    function getRoundData(uint80)
        public
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
    {
        return latestRoundData();
    }
}
"
    },
    "src/interfaces/IEOFeedAdapterBase.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title IEOFeedAdapterBase
 * @author EO
 * @notice Interface for the Feed Adapter contracts.
 */
interface IEOFeedAdapterBase {
    /**
     * @notice The enum of the feed interfaces.
     * @dev supports the following interfaces:
     *      0: MinimalAggregatorV3Interface,
     *      1: IERC4626,
     *      2: ICustomAdapter
     */
    enum FeedInterface {
        MINIMAL_AGGREGATOR_V3_INTERFACE,
        IERC4626_VAULT,
        CUSTOM_ADAPTER_INTERFACE
    }

    /**
     * @notice The struct of the feed.
     * @param feedInterface The interface of the feed
     * @param feedAddress The address of the feed.
     * @param customAdapterAddress The address of the custom adapter.
     */
    struct Feed {
        FeedInterface feedInterface;
        address feedAddress;
        address customAdapterAddress; // only used if feedInterface is CUSTOM_ADAPTER_INTERFACE
    }

    /**
     * @notice The error emitted when the feed address is invalid.
     */
    error InvalidFeedAddress();
}
"
    },
    "src/libraries/InterfaceVerifier.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { MinimalAggregatorV3Interface } from "../interfaces/MinimalAggregatorV3Interface.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { IDecimals } from "../interfaces/IDecimals.sol";
import { ICustomAdapter } from "../interfaces/ICustomAdapter.sol";

/**
 * @title InterfaceVerifier
 * @author EO
 * @notice A library for verifying the interface of the feed.
 */
library InterfaceVerifier {
    /**
     * @notice The error emitted when the MinimalAggregatorV3Interface is not implemented.
     * @param feed The address of the feed.
     */
    error MinimalAggregatorV3InterfaceNotImplemented(address feed);

    /**
     * @notice The error emitted when the IERC4626Vault is not implemented.
     * @param feed The address of the feed.
     */
    error IERC4626VaultNotImplemented(address feed);

    /**
     * @notice The error emitted when the CustomAdapter interface is not implemented.
     * @param feed The address of the feed.
     * @param adapter The address of the adapter.
     */
    error CustomAdapterInterfaceNotImplemented(address feed, address adapter);

    /**
     * @notice The error emitted when the Decimals interface is not implemented.
     * @param addr The address of the feed.
     */
    error DecimalsNotImplemented(address addr);

    /**
     * @notice Verifies the MinimalAggregatorV3Interface.
     * @dev feed latestRoundData() AND feed decimals() must be implemented, reverts otherwise.
     * @param feed The address of the feed.
     */
    function verifyMinimalAggregatorV3Interface(address feed) internal view {
        try MinimalAggregatorV3Interface(feed).latestRoundData() returns (uint80, int256, uint256, uint256, uint80) {
            _verifyDecimals(feed);
        } catch {
            revert MinimalAggregatorV3InterfaceNotImplemented(feed);
        }
    }

    /**
     * @notice Verifies the IERC4626Vault.
     * @dev feed convertToAssets() AND feed decimals() AND feed asset decimals must be implemented, reverts otherwise.
     * @param feed The address of the feed.
     */
    function verifyIERC4626(address feed) internal view {
        try IERC4626(feed).convertToAssets(10 ** IERC4626(feed).decimals()) returns (uint256) {
            _verifyDecimals(IERC4626(feed).asset());
        } catch {
            revert IERC4626VaultNotImplemented(feed);
        }
    }

    /**
     * @notice Verifies the ICustomAdapter.
     * @dev feed getCustomRate() AND (plugin getDecimals() OR feed decimals()) must be implemented.
     * @param feed The address of the feed.
     * @param adapter The address of the adapter.
     */
    function verifyICustomAdapter(address feed, address adapter) internal view {
        try ICustomAdapter(adapter).getCustomRate(feed) returns (int256) {
            try ICustomAdapter(adapter).getDecimals(feed) returns (uint8) {
                return;
            } catch {
                _verifyDecimals(feed);
            }
        } catch {
            revert CustomAdapterInterfaceNotImplemented(feed, adapter);
        }
    }

    /**
     * @notice Verifies the IDecimals.
     * @dev decimals() must be implemented, reverts otherwise.
     * @param addr The address of the contract to verify the decimals of.
     */
    function verifyDecimals(address addr) internal view {
        _verifyDecimals(addr);
    }

    /**
     * @notice Internal function to verify the IDecimals.
     * @dev decimals() must be implemented, reverts otherwise.
     * @param addr The address of the contract to verify the decimals of.
     */
    function _verifyDecimals(address addr) private view {
        try IDecimals(addr).decimals() returns (uint8) {
            return;
        } catch {
            revert DecimalsNotImplemented(addr);
        }
    }
}
"
    },
    "src/interfaces/MinimalAggregatorV3Interface.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title MinimalAggregatorV3Interface
 * @author EO
 * @notice Interface for price feed oracle
 */
interface MinimalAggregatorV3Interface {
    /**
     * @notice Returns feed decimals
     * @return The decimals of the feed.
     */
    function decimals() external view returns (uint8);

    /**
     * @notice Returns the latest round data
     * @return roundId The latest round ID, optional
     * @return answer The latest answer
     * @return startedAt The timestamp when the round started, optional
     * @return updatedAt The timestamp of the latest update
     * @return answeredInRound The round ID in which the answer was computed, optional
     */
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 */
interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   withdraw execution, and are accounted for during withdraw.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
     * through a redeem call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
     *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
     *   same transaction.
     * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
     *   redemption would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   redeem execution, and are accounted for during redeem.
     * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
    },
    "src/interfaces/IDecimals.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title IDecimals
 * @author EO
 * @notice Interface for the contract with decimals method.
 */
interface IDecimals {
    /**
     * @notice The function to get the decimals.
     * @return The decimals.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "src/interfaces/ICustomAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title ICustomAdapter
 * @author EO
 * @notice This interface is used to define the custom adapter.
 */
interface ICustomAdapter {
    /**
     * @notice This function returns the custom rate.
     * @param feedAddress The address of the asset/feed.
     * @return The custom rate.
     */
    function getCustomRate(address feedAddress) external view returns (int256);

    /**
     * @notice This function returns the decimals of the custom rate.
     * @param feedAddress The address of the asset/feed
     * @return The decimals of the custom rate
     */
    function getDecimals(address feedAddress) external view returns (uint8);
}
"
    },
    "src/interfaces/AggregatorV2V3Interface.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

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

/**
 * @title AggregatorV2V3Interface
 * @author EO
 * @notice Interface for the AggregatorV2V3 contract
 */
interface AggregatorV2V3Interface is MinimalAggregatorV3Interface {
    /**
     * @notice Returns the latest answer
     * @return The latest answer
     */
    function latestAnswer() external view returns (int256);

    /**
     * @notice Returns the latest update timestamp
     * @return The latest update timestamp
     */
    function latestTimestamp() external view returns (uint256);

    /**
     * @notice Returns the latest round ID
     * @return The latest round ID
     */
    function latestRound() external view returns (uint256);

    /**
     * @notice Returns the answer of the latest round
     * @param roundId The round ID (ignored)
     * @return The answer of the latest round
     */
    function getAnswer(uint256 roundId) external view returns (int256);

    /**
     * @notice Returns the update timestamp of the latest round
     * @param roundId The round ID (ignored)
     * @return The update timestamp of the latest round
     */
    function getTimestamp(uint256 roundId) external view returns (uint256);

    /**
     * @notice Returns the latest round data
     * @return roundId The latest round ID, optional
     * @return answer The latest answer
     * @return startedAt The timestamp when the round started, optional
     * @return updatedAt The timestamp of the latest update
     * @return answeredInRound The round ID in which the answer was computed, optional
     */
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    /**
     * @notice Returns the latest round data
     * @param roundId The round ID, is ignored
     * @return roundId The round ID of   the latest round data
     * @return answer The answer of the latest round data
     * @return startedAt The started at of the latest round data
     * @return updatedAt The updated at of the latest round data
     * @return answeredInRound The round ID in which the answer was computed of the latest round data
     */
    function getRoundData(uint80)
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.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);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.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);
}
"
    }
  },
  "settings": {
    "remappings": [
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
      "@openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
      "@maverick/v2-common/=lib/maverickprotocol-v2-common/",
      "forge-std/=lib/forge-std/src/",
      "@eoracle/=lib/target-contracts/src/",
      "@spectra-core/=lib/spectra-core/",
      "lib/spectra-core/:openzeppelin-contracts-upgradeable/=lib/spectra-core/lib/openzeppelin-contracts-upgradeable/contracts/",
      "lib/spectra-core/:openzeppelin-contracts/=lib/spectra-core/lib/openzeppelin-contracts/contracts/",
      "ds-test/=lib/spectra-core/lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
      "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/",
      "openzeppelin-erc20-basic/=lib/spectra-core/lib/openzeppelin-contracts/contracts/token/ERC20/",
      "openzeppelin-erc20-extensions/=lib/spectra-core/lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/",
      "openzeppelin-erc20/=lib/spectra-core/lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/",
      "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
      "openzeppelin-math/=lib/spectra-core/lib/openzeppelin-contracts/contracts/utils/math/",
      "openzeppelin-proxy/=lib/spectra-core/lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/",
      "openzeppelin-utils/=lib/spectra-core/lib/openzeppelin-contracts/contracts/utils/",
      "spectra-core/=lib/spectra-core/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 100
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "none",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "cancun",
    "viaIR": true
  }
}}

Tags:
ERC20, Proxy, Mintable, Yield, Upgradeable, Factory, Oracle|addr:0x1fa8573ad8f1c4fc440c7318e5a5d9d4d15e9b81|verified:true|block:23675991|tx:0xfb2d8487dbcea321948544361a5ff17298b98cd48e3b69f3c05d8efd85052d70|first_check:1761658973

Submitted on: 2025-10-28 14:42:55

Comments

Log in to comment.

No comments yet.