Oracles
Connecting smart contracts to the outside world
Oracles
Smart contracts are deterministic — given the same inputs, they always produce the same outputs. This determinism is essential for consensus, but it creates a problem: how can contracts access real-world data?
Oracles are the bridge between on-chain and off-chain worlds. They bring external data (prices, weather, sports scores, random numbers) onto the blockchain in a way that smart contracts can consume.
The Oracle Problem
Smart contracts cannot directly:
- Make HTTP requests
- Read from databases
- Access file systems
- Generate random numbers
This is by design. If contracts could access external data arbitrarily, nodes would get different results and consensus would fail.
The oracle problem is: How do we bring external data on-chain in a decentralized, trustworthy manner?
Types of Oracles
By Data Source
Software oracles — Fetch data from web APIs (prices, weather, etc.)
Hardware oracles — Read from IoT sensors and physical devices
Human oracles — Rely on human judgment (prediction markets, dispute resolution)
Computation oracles — Perform off-chain computation and return results
By Direction
Inbound oracles — Bring external data to the blockchain (most common)
Outbound oracles — Trigger external actions based on on-chain events
By Trust Model
Centralized oracles — Single source of truth (simple but risky)
Decentralized oracles — Multiple sources aggregated (more robust)
Oracle Design Patterns
Request-Response
The contract requests data, and the oracle responds asynchronously:
// 1. Contract makes a request
function requestPrice(string memory symbol) external {
bytes32 requestId = oracle.request(symbol);
pendingRequests[requestId] = msg.sender;
}
// 2. Oracle calls back with data
function fulfillPrice(bytes32 requestId, uint256 price) external onlyOracle {
address requester = pendingRequests[requestId];
// Process the price data
}Publish-Subscribe
The oracle publishes data, contracts read when needed:
interface IPriceFeed {
function latestRoundData() external view returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
contract MyContract {
IPriceFeed public priceFeed;
function getEthPrice() public view returns (int256) {
(, int256 price, , , ) = priceFeed.latestRoundData();
return price;
}
}Immediate-Read
For on-chain data that doesn't require external sources:
function getBlockTimestamp() public view returns (uint256) {
return block.timestamp;
}Chainlink: The Dominant Oracle Network
Chainlink is the most widely used oracle solution in the EVM ecosystem. It provides:
- Price Feeds — Aggregated cryptocurrency and asset prices
- VRF — Verifiable random number generation
- Automation — Trigger contracts based on time or conditions
- CCIP — Cross-chain messaging
- Functions — Custom off-chain computation
Using Chainlink Price Feeds
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PriceConsumer {
AggregatorV3Interface internal priceFeed;
constructor() {
// ETH/USD on Ethereum Mainnet
priceFeed = AggregatorV3Interface(
0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
);
}
function getLatestPrice() public view returns (int256) {
(
/* uint80 roundId */,
int256 price,
/* uint256 startedAt */,
uint256 updatedAt,
/* uint80 answeredInRound */
) = priceFeed.latestRoundData();
// Check freshness
require(block.timestamp - updatedAt < 3600, "Stale price");
return price; // 8 decimals for USD pairs
}
}Chainlink VRF (Verifiable Random Function)
For provably fair random numbers:
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
contract RandomGame is VRFConsumerBaseV2 {
// Request random number
function requestRandomWords() external returns (uint256 requestId) {
requestId = COORDINATOR.requestRandomWords(
keyHash,
s_subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
}
// Callback with random number
function fulfillRandomWords(
uint256 requestId,
uint256[] memory randomWords
) internal override {
uint256 randomNumber = randomWords[0];
// Use the random number
}
}Other Oracle Solutions
Band Protocol
Similar to Chainlink, with a focus on cross-chain data:
interface IStdReference {
function getReferenceData(
string memory base,
string memory quote
) external view returns (ReferenceData memory);
}Tellor
A permissionless oracle where anyone can submit data, with staking and dispute mechanisms:
interface ITellor {
function getDataBefore(
bytes32 queryId,
uint256 timestamp
) external view returns (bytes memory, uint256);
}Pyth Network
High-frequency price updates with sub-second latency:
interface IPyth {
function getPriceUnsafe(bytes32 id) external view returns (Price memory);
function updatePriceFeeds(bytes[] calldata updateData) external payable;
}UMA (Optimistic Oracle)
Uses an optimistic model where data is assumed correct unless disputed:
interface OptimisticOracleV2 {
function requestPrice(
bytes32 identifier,
uint256 timestamp,
bytes memory ancillaryData,
IERC20 currency,
uint256 reward
) external returns (uint256 totalBond);
}Building Your Own Oracle
For simple use cases, you can build a basic oracle:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleOracle {
address public owner;
uint256 public price;
uint256 public lastUpdated;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function updatePrice(uint256 newPrice) external onlyOwner {
price = newPrice;
lastUpdated = block.timestamp;
}
function getPrice() external view returns (uint256, uint256) {
return (price, lastUpdated);
}
}Oracle Security Considerations
Data Quality
- Stale data — Check
updatedAttimestamp - Incorrect data — Use aggregated sources, not single providers
- Missing data — Handle cases where oracle returns zero or reverts
function getPrice() public view returns (uint256) {
(, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();
require(price > 0, "Invalid price");
require(block.timestamp - updatedAt < MAX_DELAY, "Stale price");
return uint256(price);
}Flash Loan Attacks
Price oracles that read from on-chain DEXes can be manipulated with flash loans:
// VULNERABLE - uses spot price
function getEthPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1, ) = uniswapPair.getReserves();
return reserve0 / reserve1; // Can be manipulated!
}Defense: Use time-weighted average prices (TWAP) or off-chain oracles:
// SAFE - uses Chainlink
function getEthPrice() public view returns (uint256) {
(, int256 price, , , ) = chainlinkPriceFeed.latestRoundData();
return uint256(price);
}Centralization Risk
Even decentralized oracles have some centralization:
- Who controls the node operators?
- How are aggregation parameters set?
- What happens if majority of nodes collude?
Oracles on Ethereum Classic
Ethereum Classic presents unique challenges for oracles:
- Smaller ecosystem means fewer oracle providers
- Lower transaction volume may affect update frequency
- Cross-chain bridges can provide some oracle functionality
Conclusions
Oracles are essential infrastructure for connecting smart contracts to real-world data. Key takeaways:
- Use established oracle networks (Chainlink, etc.) for critical data
- Always validate oracle data (freshness, bounds, sanity checks)
- Be aware of manipulation risks, especially for on-chain price feeds
- Consider the trust model — who can update the data?
- Have fallback mechanisms for oracle failures