Oracles are third-party services that provide external data to smart contracts on the blockchain. They are a crucial component in many decentralized applications (DApps), as they allow smart contracts to interact with the real world and make decisions based on external data. In this chapter, we will explore the different types of oracles and how to work with them in Solidity.
What are Oracles?
Oracles are services that bridge the gap between the blockchain and the real world. They provide smart contracts with access to external data, such as stock prices, weather data, and sporting event results. This enables smart contracts to make decisions based on real-world data and trigger actions in the real world, such as paying out insurance claims or executing a financial trade.
Oracles can be classified into two main categories:
- On-chain oracles: These are smart contracts that are deployed on the same blockchain as the contract they are providing data to. They are typically called by the contract and return the requested data directly to it.
- Off-chain oracles: These are external services that are not deployed on the blockchain. They provide data to smart contracts through an API or other means of communication.
Why use an Oracle?
There are several reasons why you might want to use an Oracle in a smart contract:
- To access data that is not stored on the blockchain
- To trigger a contract based on external events
- To verify the authenticity of external data
Types of Oracles
There are several types of oracles, each with its own unique characteristics and use cases. Some common types of oracles include:
- Software oracles: These are oracles that provide data from software applications, such as stock prices from financial exchanges or weather data from meteorological agencies.
- Hardware oracles: These are oracles that provide data from hardware sensors or devices, such as temperature readings from smart thermostats or traffic data from smart traffic lights.
- Human oracles: These are oracles that provide data based on human input, such as sporting event results or election outcomes.
- Hybrid oracles: These are oracles that combine elements from multiple oracle types, such as a smart contract that uses both software and hardware oracles to gather data.
Working with Oracles in Solidity
There are several ways to work with oracles in Solidity, depending on the type of oracle and the data being requested. Some common approaches include:
- Web3.js: Web3.js is a JavaScript library that allows contracts to make calls to external APIs and receive data from off-chain oracles. Web3.js can be used in conjunction with HTTP, WebSockets, and other communication protocols to retrieve data from a variety of sources.
- Contract callbacks: Many on-chain oracles expose a callback function that can be called by the contract to request data. The oracle then returns the data directly to the contract through the callback function.
- Event logging: Some oracles log data to the blockchain as events, which can be listened for and parsed by the contract. This is a common approach for oracles that provide data on a regular basis, such as price feeds.
How to use an Oracle in Solidity
Using an Oracle in Solidity is relatively simple. First, you will need to find an Oracle service that provides the data you need. There are many Oracle services available, such as Oraclize and Chainlink.
Once you have chosen an Oracle service, you will need to integrate it into your smart contract. This will typically involve importing a library provided by the Oracle service and using their API to request data.
Here is an example of how to use the Oraclize Oracle in a Solidity contract:
pragma solidity ^0.7.0;
import "https://github.com/oraclize/ethereum-api/oraclizeAPI.sol";
contract MyContract is usingOraclize {
string public data;
constructor() public {
oraclize_setNetwork(networkID_ropsten);
}
function requestData() public {
oraclize_query("URL", "json(https://api.example.com).data");
}
function __callback(bytes32 _queryId, string memory _result) public {
data = _result;
}
}
In this example, we are using the Oraclize API to request data from the “https://api.example.com” URL and storing the result in the data
variable.
Conclusion
Oracles are an essential component of many decentralized applications, as they allow smart contracts to interact with the real world and make decisions based on external data. There are various types of oracles, including software, hardware, human, and hybrid oracles, and several approaches to working with them in Solidity. Understanding how to use oracles can greatly enhance the functionality and capabilities of your smart contracts.
Exercises
To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.
Write a Solidity contract that sends a request to an Oracle to retrieve the current price of a given cryptocurrency. The contract should have a function called requestPrice
that takes in a string argument representing the cryptocurrency symbol (e.g. “BTC” for Bitcoin). The function should then call the Oracle with this symbol as a parameter and return the price in wei.
pragma solidity ^0.7.0;
import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract Oracle {
function requestPrice(string memory symbol) public view returns (uint price);
}
contract CryptoPrice {
using SafeMath for uint;
Oracle oracle;
constructor(address _oracle) public {
oracle = Oracle(_oracle);
}
function requestPrice(string memory symbol) public view returns (uint price) {
return oracle.requestPrice(symbol);
}
}
Write a Solidity contract that allows users to place bets on the price of a cryptocurrency at a future date. The contract should have a function called placeBet
that takes in a string argument representing the cryptocurrency symbol (e.g. “BTC” for Bitcoin), a uint representing the amount of wei to bet, and a uint representing the timestamp of when the bet should be resolved. The function should then call the Oracle to retrieve the current price of the cryptocurrency and store it in a mapping. When the timestamp is reached, the contract should compare the current price of the cryptocurrency to the stored price and send the bet amount to the winner.
pragma solidity ^0.7.0;
import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/math/SafeMath.sol";
contract Oracle {
function requestPrice(string memory symbol) public view returns (uint price);
}
contract CryptoBet {
using SafeMath for uint;
Oracle oracle;
mapping(string => uint) public betPrices;
constructor(address _oracle) public {
oracle = Oracle(_oracle);
}
function placeBet(string memory symbol, uint amount, uint timestamp) public payable {
betPrices[symbol] = oracle.requestPrice(symbol);
require(now <= timestamp, "Bet resolution time has already passed.");
require(amount <= msg.value, "Bet amount must be equal to or greater than the value sent.");
require(amount > 0, "Bet amount must be greater than 0.");
uint currentPrice = oracle.requestPrice(symbol);
if (currentPrice > betPrices[symbol]) {
// bet was correct, send amount to winner
msg.sender.transfer(amount);
} else if (currentPrice < betPrices[symbol]) {
// bet was incorrect, send amount to contract owner
address(owner).transfer(amount);
}
// bet was a tie, do nothing
}
}
Create a Solidity contract that listens for an external event with data about the current temperature in a particular city. Use an oracle service to retrieve the data and trigger the event.
First, we need to define the event in our contract:
event NewTemperature(uint temperature);
Next, we will create a function that will be called by the oracle service to trigger the event:
function updateTemperature(uint _temperature) public {
emit NewTemperature(_temperature);
}
To retrieve the temperature data from the oracle service, we will use the oraclize_query
function. Here is an example of how to use it to get the current temperature in New York City:
oraclize_query("URL", "json(http://api.openweathermap.org/data/2.5/weather?q=New%20York,us).main.temp");
We can then parse the temperature data and call the updateTemperature
function to trigger the event:
function __callback(bytes32 _queryId, string _result) public {
uint temperature = parseInt(_result, 0);
updateTemperature(temperature);
}
Create a Solidity contract that allows users to place bets on the outcome of a sporting event. Use an oracle service to retrieve the final score and pay out the winners.
First, we will define a struct to represent a bet and an array to store all the bets:
struct Bet {
uint amount;
uint teamA;
uint teamB;
}
Bet[] public bets;
Next, we will create a function for users to place their bets:
function placeBet(uint _amount, uint _teamA, uint _teamB) public payable {
require(msg.value == _amount, "Incorrect bet amount");
bets.push(Bet(_amount, _teamA, _teamB));
}
To retrieve the final score from the oracle service, we can use the oraclize_query
function in a similar way as in the previous exercise. Once we have the final score, we can iterate through the bets array and pay out the winners:
function __callback(bytes32 _queryId, string _result) public {
uint scoreA = parseInt(_result, 0);
uint scoreB = parseInt(_result, 1);
for (uint i = 0; i < bets.length; i++) {
if (scoreA > scoreB && bets[i].teamA > bets[i].teamB) {
msg.sender.transfer(bets[i].amount);
} else if (scoreA < scoreB && bets[i].teamA < bets[i].teamB) {
msg.sender.transfer(bets[i].amount);
}
}
}
Create a smart contract that utilizes an oracle to get the current temperature in Fahrenheit for a given city. The contract should have a function that allows users to input a city name, and it should return the current temperature in Fahrenheit for that city.
pragma solidity ^0.6.0;
import "https://github.com/oraclize/ethereum-api/solidity/contracts/oraclizeAPI.sol";
// contract to get current temperature in Fahrenheit for a given city
contract TemperatureOracle {
// variable to store the current temperature in Fahrenheit
uint temperature;
// event to be emitted when the temperature is updated
event TemperatureUpdate(uint temperature);
// constructor to set the oracle address
constructor() public {
oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS);
}
// function to get the current temperature in Fahrenheit for a given city
function getTemperature(string memory city) public {
// call the oracle to get the current temperature in Fahrenheit for the given city
oraclize_query(60, "URL", "json(https://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=imperial).main.temp");
}
// callback function to handle the response from the oracle
function __callback(bytes32 queryId, string memory result, bytes memory proof) public {
// parse the result string to extract the temperature in Fahrenheit
temperature = uint(parseInt(result));
// emit the TemperatureUpdate event
emit TemperatureUpdate(temperature);
}
}