EOERC4626CappedFeedAdapter

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

import { IEOERC4626CappedFeedAdapter } from "./interfaces/IEOERC4626CappedFeedAdapter.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { InterfaceVerifier } from "./libraries/InterfaceVerifier.sol";
import { CapUtils } from "./libraries/CapUtils.sol";
import { IDecimals } from "./interfaces/IDecimals.sol";
import { EOBase } from "./EOBase.sol";

/**
 * @title EOERC4626CappedFeedAdapter
 * @author EO
 * @notice A contract that wraps ERC4626 vaults with a price cap mechanism to prevent donation attacks.
 * @dev Implements linear growth ceiling for exchange rates to prevent sudden manipulations.
 */
contract EOERC4626CappedFeedAdapter is IEOERC4626CappedFeedAdapter, EOBase, OwnableUpgradeable {
    using InterfaceVerifier for address;
    using CapUtils for CapUtils.CapConfig;

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

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

    /**
     * @notice The ERC4626 vault that supposed to be wrapped.
     */
    IERC4626 private _vault;

    /**
     * @notice The scale used for converting the rate.
     */
    uint256 private _scale;

    /**
     * @notice The cap configuration for price capping.
     */
    CapUtils.CapConfig private _capConfig;

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

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

    /**
     * @notice Initialize the adapter.
     * @param owner_ The owner of the adapter.
     * @param vault_ The ERC4626 vault address.
     * @param description_ The description of the price feed.
     * @param maxYearlyGrowthPercent_ The maximum allowed annual growth rate (0-100).
     * @param maxUpdateStep_ The maximum allowed increase in maxYearlyGrowthPercent during an update (0-100).
     */
    function initialize(
        address owner_,
        address vault_,
        string memory description_,
        uint256 maxYearlyGrowthPercent_,
        uint256 maxUpdateStep_
    )
        external
        initializer
    {
        if (vault_ == address(0)) revert InvalidFeedAddress();

        vault_.verifyIERC4626();

        __Ownable_init(owner_);

        decimals = IDecimals(IERC4626(vault_).asset()).decimals();
        description = description_;
        _scale = 10 ** IERC4626(vault_).decimals();
        _vault = IERC4626(vault_);

        // Set up the cap configuration with snapshot values
        uint256 currentRate = IERC4626(vault_).convertToAssets(_scale);
        _capConfig.setCapConfig(currentRate, maxYearlyGrowthPercent_, maxUpdateStep_);
    }

    /**
     * @notice Set the maximum yearly growth percent.
     * @param maxYearlyGrowthPercentNew The new maximum yearly growth percent.
     */
    function setMaxYearlyGrowthPercent(uint256 maxYearlyGrowthPercentNew) external onlyOwner {
        uint256 maxYearlyGrowthPercent = _capConfig.maxYearlyGrowthPercent;
        _capConfig.updateMaxYearlyGrowthPercent(maxYearlyGrowthPercentNew);
        emit MaxYearlyGrowthPercentUpdated(maxYearlyGrowthPercent, maxYearlyGrowthPercentNew);
    }

    /**
     * @notice Get the underlying vault.
     * @return The ERC4626 vault.
     */
    function getVault() external view returns (IERC4626) {
        return _vault;
    }

    /**
     * @notice Get the cap configuration.
     * @return The cap configuration.
     */
    function getCapConfig() external view returns (CapUtils.CapConfig memory) {
        return _capConfig;
    }

    /**
     * @notice Returns latest round data with price capping applied.
     * @dev Implements the three-step process: fetch price, compare with max rate, return adjusted price.
     * @return roundId The round id, returns 0
     * @return answer The capped answer (minimum of current rate and maximum allowed rate)
     * @return startedAt The started at, returns 0
     * @return updatedAt The updated at, returns current block timestamp
     * @return answeredInRound The answered in round, returns 0
     */
    function latestRoundData() public view override returns (uint80, int256, uint256, uint256, uint80) {
        uint256 currentRate = _vault.convertToAssets(_scale);

        uint256 cappedRate = _capConfig.getCappedRate(currentRate);

        return (0, int256(cappedRate), 0, block.timestamp, 0);
    }
}
"
    },
    "src/interfaces/IEOERC4626CappedFeedAdapter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { CapUtils } from "../libraries/CapUtils.sol";

/**
 * @title IEOERC4626CappedFeedAdapter
 * @author EO
 * @notice Interface for the EOERC4626CappedFeedAdapter contract.
 */
interface IEOERC4626CappedFeedAdapter {
    /**
     * @notice Event emitted when maxYearlyGrowthPercent is updated.
     * @param oldValue The old maxYearlyGrowthPercent value.
     * @param newValue The new maxYearlyGrowthPercent value.
     */
    event MaxYearlyGrowthPercentUpdated(uint256 oldValue, uint256 newValue);

    /**
     * @notice The error emitted when an invalid feed address is provided.
     */
    error InvalidFeedAddress();

    function initialize(
        address owner_,
        address vault_,
        string memory description_,
        uint256 maxYearlyGrowthPercent_,
        uint256 maxUpdateStep_
    )
        external;

    function setMaxYearlyGrowthPercent(uint256 maxYearlyGrowthPercentNew) external;

    function getVault() external view returns (IERC4626);

    function getCapConfig() external view returns (CapUtils.CapConfig memory);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "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/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/libraries/CapUtils.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title CapUtils
 * @author EO
 * @notice Utility library for implementing growth rate caps and manipulation protection in price feeds
 */
library CapUtils {
    struct CapConfig {
        uint256 snapshotRatio;
        uint256 snapshotTime;
        uint256 maxYearlyGrowthPercent;
        uint256 maxUpdateStep;
        uint256 lastUpdateTime;
    }

    uint256 private constant PERCENT_MULTIPLIER = 100;
    uint256 private constant SECONDS_PER_YEAR = 365 days;
    uint256 private constant PERCENT_UPDATE_FREQUENCY = 1 weeks;

    // Custom errors for the library
    error InvalidMaxYearlyGrowthPercent();
    error UpdateFrequencyNotRespected();
    error UpdateStepLimitExceeded();
    error InvalidMaxUpdateStep();
    error InvalidSnapshotRatio();

    /**
     * @notice Set the cap configuration.
     * @param capConfig The cap configuration to set.
     * @param snapshotRatio The snapshot ratio.
     * @param maxYearlyGrowthPercent The maximum yearly growth percent.
     * @param maxUpdateStep The maximum update step.
     */
    function setCapConfig(
        CapConfig storage capConfig,
        uint256 snapshotRatio,
        uint256 maxYearlyGrowthPercent,
        uint256 maxUpdateStep
    )
        internal
    {
        if (snapshotRatio == 0) {
            revert InvalidSnapshotRatio();
        }

        if (!_isValidPercent(maxYearlyGrowthPercent)) {
            revert InvalidMaxYearlyGrowthPercent();
        }
        if (!_isValidPercent(maxUpdateStep)) {
            revert InvalidMaxUpdateStep();
        }

        capConfig.snapshotRatio = snapshotRatio;
        capConfig.snapshotTime = block.timestamp;
        capConfig.maxYearlyGrowthPercent = maxYearlyGrowthPercent;
        capConfig.maxUpdateStep = maxUpdateStep;
        capConfig.lastUpdateTime = block.timestamp;
    }

    /**
     * @notice Update the maximum yearly growth percent with validation.
     * @param capConfig The cap configuration to update.
     * @param newMaxYearlyGrowthPercent The new maximum yearly growth percent.
     */
    function updateMaxYearlyGrowthPercent(CapConfig storage capConfig, uint256 newMaxYearlyGrowthPercent) internal {
        if (!_isValidPercent(newMaxYearlyGrowthPercent)) {
            revert InvalidMaxYearlyGrowthPercent();
        }
        if (!_isUpdateFrequencyRespected(capConfig.lastUpdateTime)) {
            revert UpdateFrequencyNotRespected();
        }
        if (
            !_isMaxYearlyGrowthPercentUpdateValid(
                capConfig.maxYearlyGrowthPercent, newMaxYearlyGrowthPercent, capConfig.maxUpdateStep
            )
        ) {
            revert UpdateStepLimitExceeded();
        }

        capConfig.maxYearlyGrowthPercent = newMaxYearlyGrowthPercent;
        capConfig.lastUpdateTime = block.timestamp;
    }

    /**
     * @notice Calculate the maximum allowable rate based on linear growth formula.
     * @dev Formula: MaximumRate(t) = SnapshotRatio + (SnapshotRatio × MaxYearlyGrowthPercent × TimeElapsed) / (100 ×
     * 365 days)
     * @param capConfig The cap configuration.
     * @return The maximum allowable rate.
     */
    function calculateMaxRate(CapConfig memory capConfig) internal view returns (uint256) {
        if (block.timestamp <= capConfig.snapshotTime) {
            return capConfig.snapshotRatio;
        }

        uint256 timeElapsed = block.timestamp - capConfig.snapshotTime;
        uint256 growthAmount = (capConfig.snapshotRatio * capConfig.maxYearlyGrowthPercent * timeElapsed)
            / (PERCENT_MULTIPLIER * SECONDS_PER_YEAR);

        return capConfig.snapshotRatio + growthAmount;
    }

    /**
     * @notice Get the capped rate by comparing current rate with maximum allowed rate.
     * @param capConfig The cap configuration.
     * @param currentRate The current exchange rate from the vault.
     * @return The minimum of current rate and maximum allowed rate.
     */
    function getCappedRate(CapConfig memory capConfig, uint256 currentRate) internal view returns (uint256) {
        uint256 maxRate = calculateMaxRate(capConfig);
        return currentRate > maxRate ? maxRate : currentRate;
    }

    /**
     * @notice Validate that the update frequency is respected.
     * @param lastUpdateTime The timestamp of the last update.
     * @return True if enough time has passed since the last update.
     */
    function _isUpdateFrequencyRespected(uint256 lastUpdateTime) private view returns (bool) {
        return block.timestamp >= lastUpdateTime + PERCENT_UPDATE_FREQUENCY;
    }

    /**
     * @notice Validate that the new growth percent is within the allowed step range.
     * @param oldPercent The current growth percent.
     * @param newPercent The proposed new growth percent.
     * @param maxUpdateStep The maximum allowed step (in percentage points).
     * @return True if the update is within the allowed range.
     */
    function _isMaxYearlyGrowthPercentUpdateValid(
        uint256 oldPercent,
        uint256 newPercent,
        uint256 maxUpdateStep
    )
        private
        pure
        returns (bool)
    {
        if (newPercent > oldPercent) {
            return (newPercent - oldPercent) <= maxUpdateStep;
        } else {
            return (oldPercent - newPercent) <= maxUpdateStep;
        }
    }

    /**
     * @notice Validate that the maxUpdateStep is within valid bounds (0-100).
     * @param percent The percent to validate.
     * @return True if the percent is valid.
     */
    function _isValidPercent(uint256 percent) private pure returns (bool) {
        return percent <= PERCENT_MULTIPLIER;
    }
}
"
    },
    "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/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();
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "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
        }
    }
}
"
    },
    "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);
}
"
    },
    "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);
}
"
    },
    "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);
}
"
    }
  },
  "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, Multisig, Mintable, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x7b20c49c52b6b7bb2f6d361a7c045f967922a574|verified:true|block:23675962|tx:0x7ab2028fab35e890de52c32b29be669d30f4bced9b386dc04caca33c5e4e1478|first_check:1761658609

Submitted on: 2025-10-28 14:36:51

Comments

Log in to comment.

No comments yet.