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
);
}
}
Submitted on: 2025-09-29 13:59:28
Comments
Log in to comment.
No comments yet.