USDTUSDCOracle

Description:

Smart contract deployed on Ethereum with Oracle features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

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

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

  function version() external view returns (uint256);

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

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

/**
 * @title USDT/USDC Oracle
 * @dev Custom oracle that composes Chainlink USDT/USD and USDC/USD feeds to provide USDT/USDC rate
 * @notice This contract follows the Chainlink AggregatorV3Interface
 */
contract USDTUSDCOracle is IAggregatorV3Interface {
    IAggregatorV3Interface private immutable usdtUsdPriceFeed;
    IAggregatorV3Interface private immutable usdcUsdPriceFeed;
    
    uint8 public constant override decimals = 18;
    string public constant description = "USDT / USDC";
    uint256 public constant override version = 1;
    
    // Events
    event PriceUpdated(int256 price, uint256 timestamp);
    
    // Errors
    error InvalidPriceData();
    error StalePrice();
    error NegativePrice();
    error InvalidFeedAddress();
    
    // Constants for staleness check (3 hours)
    uint256 private constant STALENESS_THRESHOLD = 10800;
    
    /**
     * @dev Constructor sets the Chainlink price feed addresses
     * @param _usdtUsdPriceFeed Address of the USDT/USD Chainlink price feed
     * @param _usdcUsdPriceFeed Address of the USDC/USD Chainlink price feed
     */
    constructor(address _usdtUsdPriceFeed, address _usdcUsdPriceFeed) {
        if ((_usdtUsdPriceFeed == address(0)) || (_usdcUsdPriceFeed == address(0))) revert InvalidFeedAddress();
        
        usdtUsdPriceFeed = IAggregatorV3Interface(_usdtUsdPriceFeed);
        usdcUsdPriceFeed = IAggregatorV3Interface(_usdcUsdPriceFeed);
    }
    
    /**
     * @dev Returns the latest round data for USDT/USDC
     * @return roundId The round ID (based on USDT feed)
     * @return answer The USDT/USDC price with 18 decimals
     * @return startedAt Timestamp when the round started (min of both feeds)
     * @return updatedAt Timestamp when the round was updated (min of both feeds)
     * @return answeredInRound The round ID in which the answer was computed
     */
    function latestRoundData()
        external
        view
        override
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        // Get data from both price feeds
        (
            uint80 usdtRoundId,
            int256 usdtPrice,
            uint256 usdtStartedAt,
            uint256 usdtUpdatedAt,
            uint80 usdtAnsweredInRound
        ) = usdtUsdPriceFeed.latestRoundData();
        
        (
            ,
            int256 usdcPrice,
            uint256 usdcStartedAt,
            uint256 usdcUpdatedAt,
            
        ) = usdcUsdPriceFeed.latestRoundData();
        
        // Validate prices
        if (usdtPrice <= 0 || usdcPrice <= 0) {
            revert NegativePrice();
        }
        
        // Check for stale prices
        if (
            block.timestamp - usdtUpdatedAt > STALENESS_THRESHOLD ||
            block.timestamp - usdcUpdatedAt > STALENESS_THRESHOLD
        ) {
            revert StalePrice();
        }
                
        // Calculate USDT/USDC = (USDT/USD) / (USDC/USD)
        int256 usdtUsdcPrice = _normalizePrice(usdtPrice, usdcPrice);

        // Return data using USDT feed's metadata with calculated price
        return (
            usdtRoundId,
            usdtUsdcPrice,
            usdtStartedAt < usdcStartedAt ? usdtStartedAt : usdcStartedAt, // min startedAt
            usdtUpdatedAt < usdcUpdatedAt ? usdtUpdatedAt : usdcUpdatedAt, // min updatedAt
            usdtAnsweredInRound
        );
    }

    function _normalizePrice(int256 usdtPrice, int256 usdcPrice) private view returns (int256) {
        // Calculate USDT/USDC price with proper decimal handling
        uint8 usdtDecimals = usdtUsdPriceFeed.decimals();
        uint8 usdcDecimals = usdcUsdPriceFeed.decimals();
        
        // Normalize prices to 18 decimals for calculation
        uint256 normalizedUsdtPrice = uint256(usdtPrice) * (10 ** (18 - usdtDecimals));
        uint256 normalizedUsdcPrice = uint256(usdcPrice) * (10 ** (18 - usdcDecimals));
        
        // Calculate USDT/USDC = (USDT/USD) / (USDC/USD)
        int256 usdtUsdcPrice = int256((normalizedUsdtPrice * 1e18) / normalizedUsdcPrice);

        return usdtUsdcPrice;
    }
    
    /**
     * @dev Returns historical round data
     * @notice This implementation returns the latest data regardless of roundId
     * @param _roundId The round ID to query (ignored in this implementation)
     */
    function getRoundData(uint80 _roundId)
        external
        view
        override
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )
    {
        // For simplicity, return latest data for any round query
        return this.latestRoundData();
    }
    
    /**
     * @dev Get the current USDT/USDC price
     * @return The current price with 18 decimals
     */
    function getPrice() external view returns (int256) {
        (, int256 price, , , ) = this.latestRoundData();
        return price;
    }
    
    /**
     * @dev Get the addresses of the underlying price feeds
     * @return usdtFeed Address of USDT/USD price feed
     * @return usdcFeed Address of USDC/USD price feed
     */
    function getUnderlyingFeeds() external view returns (address usdtFeed, address usdcFeed) {
        return (address(usdtUsdPriceFeed), address(usdcUsdPriceFeed));
    }
    
    /**
     * @dev Check if the price data is fresh
     * @return true if both underlying feeds have fresh data
     */
    function isFresh() external view returns (bool) {
        (, , , uint256 usdtUpdatedAt, ) = usdtUsdPriceFeed.latestRoundData();
        (, , , uint256 usdcUpdatedAt, ) = usdcUsdPriceFeed.latestRoundData();
        
        return (
            block.timestamp - usdtUpdatedAt <= STALENESS_THRESHOLD &&
            block.timestamp - usdcUpdatedAt <= STALENESS_THRESHOLD
        );
    }
}

Tags:
Oracle|addr:0xaec1093f2998ca92707a911c940ab1cb42e74018|verified:true|block:23468411|tx:0x54a3dc70192bd3ec523d5fe8f71236513995171807ba7531c1a9eb365b689c36|first_check:1759147166

Submitted on: 2025-09-29 13:59:28

Comments

Log in to comment.

No comments yet.