Ethereum Oracle Contracts: Setup and Orientation
Oracles are changing the way we view and create smart contracts. How do we implement them in Ethereum, using nothing but Solidity and standard dev tools?
In the first article of our three-part series, Toptal Blockchain Developer John R. Kosinski explains how to get started and compile a smart contract with oracles.
Oracles are changing the way we view and create smart contracts. How do we implement them in Ethereum, using nothing but Solidity and standard dev tools?
In the first article of our three-part series, Toptal Blockchain Developer John R. Kosinski explains how to get started and compile a smart contract with oracles.
As a full-stack dev for nearly two decades, John’s worked with IoT, Blockchain, web, and mobile projects using C/C++, .NET, SQL, and JS.
Expertise
PREVIOUSLY AT
Note: This guide remains here for historical purposes, but Truffle has been sunset as of September, 2023.
Ethereum Smart Contracts are more than just “the new hot thing.” It’s my belief that they (or something related) are poised to change the way that humans do business with one another in the upcoming new age of the internet. Time will tell if that’s the case.
This is the first of a three-part article on Ethereum smart contract development with Solidity, most specifically exploring the use of contracts with so-called “oracles”—which are basically contracts which pump data into the blockchain for use by other smart contracts.
- Part 1: An introduction to development with Truffle, and project setup for further experimentation
- Part 2: Delving into the code for deeper examination
- Part 3: A conceptual discussion of oracles with smart contracts
The goal of this, part 1 of the series, is not to get much into the concept of oracle contracts, the philosophy behind them, or even very deeply into what they are; the goal of this part of our Ethereum oracle tutorial is simply to:
- Get you set up with building smart contracts with Truffle.
- Build a smart contract project which will serve us in parts 2 and 3.
- Introduce a few concepts related to Ethereum smart contracts and coding of smart contracts.
- Introduce the compile/run/debug cycle with Truffle and smart contracts.
Definition: Oracle. A means for smart contracts to access data from the world outside the blockchain. A type of smart contract themselves, oracles take data from the outside world and put it into the blockchain for other smart contracts to consume.
The first part of this article will consist of getting set up with all the prerequisites. Then, we’ll set up a single Ethereum contract and test it with Truffle. Finally, we’ll separate the oracle from the client and test them jointly.
Software Requirements
- Any major OS will work, though some of the installation and setup will of course be different on different systems. I have done all of this on Ubuntu Linux (16.04). I have also had no problems setting up the environment on Windows. I have not tried Mac, though I am aware that it’s common to do so on Mac as well.
- It is not necessary to run a full eth node; we will use Truffle, which comes with its own testnet. If you know a bit about what you’re doing, you can use any other testnet of your choosing; Truffle’s local dev testnet is just the easiest and most accessible for purposes of this tutorial.
Knowledge Requirements
- Basic knowledge of how blockchain works
- Understanding of what a blockchain-based smart contract is
- Some basic hello-worldish experience with smart contract development will be helpful, but not necessary if you’re smart and ambitious. (And I know that you are!)
This article series can serve as a very first introduction to smart contracts, but it ramps up very quickly into more advanced concepts. If it’s your first eth smart contract tutorial, be prepared to climb to altitude quickly. If you feel confident, great; if not, feel free to get a simpler “hello world” type of tutorial or two under your belt first. Check out one of or previous Ethereum articles and Cryptozombies, for starters.
Caveat: The smart contract space, being so new, changes quickly. Solidity syntax features that were new when this article was written may be deprecated or obsolete by the time you’re reading this. Geth versions may have come and go. Solidity is always adding new language features and deprecating old ones. Many new features are currently in the works. So, be prepared if necessary to adapt the information in this article to the new landscape of the future; if you’re serious about learning smart contract development, then I have faith in you.
Description of Example App
Use case: Users bet on boxing matches.
- The user can pull a list of bet-able boxing matches.
- The user can choose a match and place a bet on the winner.
- The user can bet any amount above a specified minimum.
- If the user’s pick loses, the user loses the entire amount of the bet.
- If the user’s pick wins, the user gets a portion of the pot based on the size of his/her bet and the total amount bet on the loser of the match, after the house (the contract owner) takes a small percentage of the winnings.
What Is an Ethereum Oracle?
Smart contracts are still kind of a new thing; they’ve yet to take the mainstream, and so many aspects of how they will work have not yet been hammered out and standardized. I will briefly explain the impetus behind the idea of the “oracle”—and be patient; we’ll get into it in more depth in later parts.
Engineering a blockchain contract is not like programming a client-server app. One important difference is that data with which the contract interacts, must already be on the blockchain. There is no calling out of the blockchain. Not only is it not supported by the language, it’s not supported by the blockchain paradigm. The contract can take bets in the form of Ethereum-based currency, store them in the contract, and release them to the correct wallet addresses according to a formula, when the winner of a match is declared. But how does the contract know the winner? It can’t query a REST API or anything like that. It can only use data that’s already in the blockchain! Many many use cases of smart contracts run into a similar problem—they are seriously limited unless they can interact with the world outside the blockchain.
If the contract can only interact with data on the blockchain, an obvious solution is to inject the necessary data into the blockchain. And that’s what an oracle is. An oracle is another contract, which injects data into the blockchain, allowing other contracts to consume it. While that may raise questions about trust and trustlessness, just accept for now that that’s what an oracle is. In part 3 of this series, we’ll discuss those nuances. In our example use case, the oracle will be the contract that injects data into the blockchain, regarding (a) what matches are available and (b) who won those matches, once decided.
Setting Up the Ethereum Development Environment
For basic setup, we will install:
- Geth (optional for now)
- Truffle
- Ganache CLI (optional)
- A development environment (optional)
This article does not have the space to be a full guide to environment setup but acts as just a rough guide. That’s ok, though, because there are already plenty of more complete setup guides for your particular OS and the internet doesn’t really need a new one. So I will take you quickly down the path and point you toward some resources for getting more details as needed. Be prepared to install requirements and prereqs as your system requires and as Google directs you.
Install Geth (optional)
Geth is Go-ethereum, the Ethereum core software; while it’s not necessary for this exercise at all, it would behoove any would-be Ethereum developer to have it and be familiar with it. It will be necessary if you’re ever going to deploy your smart contract to the live Ethereum network.
- http://www.talkcrypto.org/blog/2018/01/23/what-is-geth/
- https://github.com/ethereum/go-ethereum/wiki/Installation-Instructions-for-Ubuntu
- https://github.com/ethereum/go-ethereum/wiki/Installation-instructions-for-Windows
Install Truffle
Truffle is the main thing we’re going to use for development, and absolutely is a requirement for this guide.
Install Ganache CLI (optional)
I recommend installing Ganache CLI to use as another testing tool, though we won’t actually use it for our tutorial. It’s optional.
Ethereum Development Environment
It would be more than possible to do this whole tutorial with any simple text editor, like Notepad++, gedit, vi, or any text editor or IDE of your choosing. I personally am using Visual Studio Code with the following extensions:
- Solidity
- Solidity extended
- Material icon theme
Note: The extensions are not required—they just make for a better coding environment.
Setting Up the Code
Project Setup
Truffle is a very convenient tool for compiling smart contracts, migrating them to a blockchain, and also it provides development and debugging utilities. Some project setup will be necessary in order to integrate with Truffle. Now we’ll set up the shell for our project, both in Truffle and in the directory structure. Just sit back, follow the steps robotically for now, and enjoy.
Create a directory to house all the code; call it oracle-example.
Inside the root directory, create two subdirectories, because eventually, the project will consist of two sub-projects. Create the directories:
- /oracle-example/client
- /oracle-example/oracle
Go into the client folder, because that’s the first project we’re going to develop. Open a terminal (command line) window in the /oracle-example/client folder.
Run the command truffle init
.
Note that among many files created are truffle-config.js and truffle.js. We don’t need both of them, so delete truffle-config.js (just to avoid confusion and clutter).
We need to edit truffle.js, in order to point Truffle in the right direction for testing. Replace the contents of truffle.js with the following:
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
}
}
};
https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/truffle.js
Note that Truffle init created a directory called migrations (oracle-example/client/migrations). Inside that folder should be a file named 1_initial_migration.js.
Add another file in the migrations directory and name it 2_deploy_contracts.js, with the following content:
var BoxingBets = artifacts.require("BoxingBets");
module.exports = function(deployer) {
deployer.deploy(BoxingBets);
};
https://github.com/jrkosinski/oracle-example/tree/part1-step1
Adding the Code
Now that the simple setup is out of the way, we’re set to begin coding. Remember, this part of the article is still introduction and setup, so we’re going to go rather quickly through the code. We’ll get into more in-depth explanations of the code in part 2, and more in-depth discussion of the architecture and concept in part 3. That said, we’ll touch quickly upon some core concepts evident in the code. Follow carefully to keep up.
The full code for this step in the process is available on GitHub: https://github.com/jrkosinski/oracle-example/tree/part1-step1
Contracts in Solidity
A “contract” in Solidity is roughly analogous to a class in other object-oriented languages. The language itself has been compared to Golang and JavaScript, among others. Some other language constructs in Solidity—which we’ll have examples of later—are modifiers, libraries, and interfaces. Inheritance (including multiple inheritance) is supported for contracts. Solidity contract files have a .sol extension.
Oracle Interface
Add this file to your project: /oracle-example/client/contracts/OracleInterface.sol
https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/contracts/OracleInterface.sol
Normally, the oracle interface would be just that—an interface. For this very first iteration, it’s just a simple class contained within the Solidity project, just as a placeholder for now. We’ll move it out in the very next step, after we successfully compile and run the contract on Truffle. After we convert this to an actual interface later on, the function implementations will be empty.
Client Contract
Add this file to your project: /oracle-example/client/contracts/BoxingBets.sol
https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/contracts/BoxingBets.sol
This is the contract which consumes the boxing match data, allows users to query available matches, and place bets on them. In later iterations, it will calculate and pay out winnings.
Compiling and Running
Now is when we’ll see if we got everything right the first time!
Compile and Migrate the Contract
Open a terminal in the /oracle-example/client/ folder
Compile the code with this command:
truffle compile
Alternate: Use my recompile.sh shell script (https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/recompile.sh).
Note that you will see a lot of warnings, because our code is not yet in its final form!
Open the Truffle development console:
truffle develop
Now, in the Truffle developer console, migrate to the test network:
truffle(develop)> migrate
Run the Contract
At the development console prompt, enter the following line of code:
truffle(develop)> BoxingBets.deployed().then(inst => { instance = inst })
Now, “instance” is the variable which refers to the BoxingBets contract and can be used to call its public methods.
Test it using the following command:
truffle(develop)> instance.test(3, 4)
Note that we’ve included a public “test” function in BoxingBets.sol. It adds together whatever two numbers you pass to it, just to demonstrate that the contract is executing code, and that we can call it from the Truffle development console. If we get a sane-looking response (see below) then our job here is done (for now at least).
Separate the Ethereum Oracle
If everything has succeeded so far, then we’re over the hump. The next thing we’ll do is separate the oracle contract from the BoxingBets contract. In real usage, the oracle’s contract will exist separately from the client contract on the blockchain, so we’ll need to be able to:
- Instantiate it by blockchain address.
- Dynamically change the oracle address that the client contract uses to reference the oracle.
So in short, what we’re going to do now is separate the oracle and the client into two separate blockchain contract entities, and make them talk to each other. The client will instantiate the oracle by address and call it.
Client Contract
First, we’re going to change the client contract (client) so that it refers to a dynamic interface to an oracle rather than a concrete class. Then we’ll make sure that it instantiates the oracle from an outside contract.
Go into /oracle-example/client/contracts/OracleInterface.sol. As we noted before, this is currently not an interface, but we’re about to make it one. Replace what’s in there with the contents of:
https://github.com/jrkosinski/oracle-example/tree/part1-step2/client/contracts/OracleInterface.sol
pragma solidity ^0.4.17;
contract OracleInterface {
enum MatchOutcome {
Pending, //match has not been fought to decision
Underway, //match has started & is underway
Draw, //anything other than a clear winner (e.g. cancelled)
Decided //index of participant who is the winner
}
function getPendingMatches() public view returns (bytes32[]);
function getAllMatches() public view returns (bytes32[]);
function matchExists(bytes32 _matchId) public view returns (bool);
function getMatch(bytes32 _matchId) public view returns (
bytes32 id,
string name,
string participants,
uint8 participantCount,
uint date,
MatchOutcome outcome,
int8 winner);
function getMostRecentMatch(bool _pending) public view returns (
bytes32 id,
string name,
string participants,
uint participantCount,
uint date,
MatchOutcome outcome,
int8 winner);
function testConnection() public pure returns (bool);
function addTestData() public;
}
In BoxingBets.sol, we’re going to replace this line:
OracleInterface internal boxingOracle = new OracleInterface();
With these two lines:
address internal boxingOracleAddr = 0;
OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr);
Now what we want is a way to set the address of the oracle, dynamically, and a function that we can call to find out the current oracle address. Add these two functions to BoxingBets.sol:
/// @notice sets the address of the boxing oracle contract to use
/// @dev setting a wrong address may result in false return value, or error
/// @param _oracleAddress the address of the boxing oracle
/// @return true if connection to the new oracle address was successful
function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) {
boxingOracleAddr = _oracleAddress;
boxingOracle = OracleInterface(boxingOracleAddr);
return boxingOracle.testConnection();
}
/// @notice gets the address of the boxing oracle being used
/// @return the address of the currently set oracle
function getOracleAddress() external view returns (address) {
return boxingOracleAddr;
}
And finally, for testing the connection between the client and the oracle, we can replace the test function in BoxingBets with a function to test the oracle connection:
/// @notice for testing; tests that the boxing oracle is callable
/// @return true if connection successful
function testOracleConnection() public view returns (bool) {
return boxingOracle.testConnection();
}
Ownable
Notice that the definition for setOracleAddress
has an onlyOwner
modifier following it. That restricts this function from being called by anyone other than the contract’s owner, even though the function is public. That is not a language feature. That’s provided to us by the Ownable contract, which is lifted out of OpenZeppelin’s library of general-utility Solidity contracts. We will get into the details of that in Part 2, but in order to facilitate the use of that onlyOwner
modifier, we need to make a few changes:
Copy Ownable.sol from https://github.com/jrkosinski/oracle-example/tree/part1-step2/client/contracts/Ownable.sol into /oracle-example/client/contracts/.
Add a reference to it at the top of BoxingBets.sol, like so:
import "./Ownable.sol";
(You can add it just under the line that imports OracleInterface.sol.)
Modify the contract declaration of BoxingBets to make it inherit from Ownable, from this:
contract BoxingBets {
To this:
contract BoxingBets is Ownable {
And we should be all set. Full code is here in case you got lost: https://github.com/jrkosinski/oracle-example/tree/part1-step2/client/contracts
Oracle Contracts
Setup
Now that the BoxingBets contract is attempting to refer to a completely separate contract (that is the oracle) by address, our next job is to create that oracle contract. So now we’re going to create a whole separate project that will contain the oracle contract. It’s essentially the same setup that we’ve already done for the client contract project; that is, setting up Truffle for compiling and developing.
You should already have a folder called /oracle-example/oracle/ which we created in a previous step (or if not, go ahead and create that empty directory now). Open a terminal in that directory.
- Run the command
truffle init
. - Delete /oracle-example/oracle/truffle-config.js.
- Edit /oracle-example/oracle/truffle.js like so:
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
}
}
};
See the example here: https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/truffle.js
Inside /oracle-example/oracle/migrations/, create a file called 2_deploy_contracts.js, with the following content:
var BoxingOracle = artifacts.require("BoxingOracle");
module.exports = function(deployer) {
deployer.deploy(BoxingOracle);
};
See the example here: https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/migrations/2_deploy_contracts.js
Oracle Code
For this step, simply copy the following three files from https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/contracts/ into your /oracle-example/oracle/contracts/
folder:
- BoxingOracle.sol: The main oracle contract.
- Ownable.sol: For owner-only functions, as we used in the client contract already.
- DateLib.sol: A date library. We’ll look at it in more depth in Part 2 of this series.
Testing the Oracle
Now, in the project’s current iteration, we really need to thoroughly test our smart contract oracle, since that will be our base on which we’ll build the rest of the project. So, now that we’ve set up the oracle project and copied the code, we will want to:
- Compile the oracle.
- Make sure that the oracle runs.
- Run a few functions in the Truffle console to ensure that the oracle is working as expected.
Compile and Migrate the Oracle
Still in a terminal open to /oracle-example/oracle/
, run the following commands. Again, these steps are identical to what we’ve already done to compile and migrate the client contract.
truffle compile
Alternate: Use my recompile.sh shell script (https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/recompile.sh).
Open the Truffle development console:
truffle develop
Migrate to the test network:
truffle(develop)> migrate
Run and Test the Oracle
Still in the Truffle development console, enter this to capture a usable pointer to the oracle contract:
truffle(develop)> BoxingOracle.deployed().then(inst => { instance = inst })
Now we can (and should) run a suite of tests on our oracle contract to test it. Try running the following commands, each in turn, and examine the results.
truffle(develop)> instance.testConnection()
...
truffle(develop)> instance.getAllMatches()
...
truffle(develop)> instance.addTestData()
...
truffle(develop)> instance.getAllMatches()
...
You are encouraged at this point to have a look through the oracle code, see what public methods are available, read the comments in the code, and come up with some of your own tests to run (and run them here in the console, as shown above).
Testing and Debugging
Now we’re ready for the final test: to test that the client contract can call the oracle contract that’s already on the blockchain, and pull in and use its data. If all of this works, then we have a client-oracle pair that we can use for further experimentation. Our steps to run the end-to-end test:
- Compile and run the oracle contract
- Compile and run the client contract
- Get the address of the oracle contract
- Set the oracle address in the client contract
- Add test data to the oracle contract
- Test that we can retrieve that data in the client contract
Open two terminal windows:
- One in /oracle-example/client/
- And the other in /oracle-example/oracle/
I suggest that you keep the /oracle-example/client/ one open on the left and the /oracle-example/oracle/ one open on the right, and follow along closely to avoid confusion.
Compile and Run the Oracle Contract
Execute the following commands in the /oracle-example/oracle/ terminal:
bash recompile.sh
truffle develop
truffle(develop)> migrate
truffle(develop)> BoxingOracle.deployed().then(inst => { instance = inst })
Compile and Run the Client Contract
Execute the following commands in the /oracle-example/client/ terminal:
bash recompile.sh
truffle develop
truffle(develop)> migrate
truffle(develop)> BoxingBets.deployed().then(inst => { instance = inst })
Get the Address of the Oracle Contract
Execute the following command to Truffle in the /oracle-example/oracle/ terminal:
truffle(develop)> instance.getAddress()
Copy the address which is the output from this call; and use it in the next step.
Set the Oracle Address in the Client Contract
Execute the following command to truffle in the /oracle-example/client/ terminal:
truffle(develop)> instance.setOracleAddress('<insert address here, single quotes included>')
And test it:
truffle(develop)> instance.testOracleConnection()
If the output is true
, then we’re good to go.
Test that we can Retrieve that Data in the Client Contract
Execute the following command to truffle in the /oracle-example/client/ terminal:
truffle(develop)> instance.getBettableMatches()
It should return an empty array, because no test data has yet been added to the oracle side.
Execute the following command to truffle in the /oracle-example/oracle/ terminal to add test data:
truffle(develop)> instance.addTestData()
Execute the following command to truffle in the /oracle-example/client/ terminal, to see if we can pick up the newly added test data from the client:
truffle(develop)> instance.getBettableMatches()
Now, if you take individual addresses from the array returned by getBettableMatches()
, and plug them into getMatch()
.
You are encouraged at this point to have a look through the client code, see what public methods are available, read the comments in the code, and come up with some of your own tests to run (and run them here in the console, as above).
Conclusion of Part One
Our results from this exercise are limited, but then so were our goals, in order to keep a realistic pace. Our client does not yet have the ability to take bets, handle funds, divvy up the winnings, etc. What we do have—aside from the knowledge and experienced gained—is:
- A mostly functional smart contract oracle
- A client that can connect to and interact with the oracle
- A framework for further development and learning
And that’s not too bad for a short article.
In part two of this series, we will delve more deeply into the code and look at some of the features that are unique to smart contract development as well as some of the language features which are specific to Solidity. Many of the things that were just glossed over in this part will be explained in the next.
In part three of this series, we will discuss a bit about the philosophy and design of smart contracts, specifically in relation to their use with oracles.
Further Optional Steps
Solo experimentation is a good way to learn. Here are a few simple suggestions if you’re thinking of ways to extend this tutorial for greater knowledge (none of the following will be covered in Parts 2 and 3):
- Deploy the contracts to Ganache (formerly testrpc) and run the same tests to verify function.
- Deploy the contracts to ropsten or rinkeby testnets and run the same tests to verify function.
- Build a web3js front end for either the oracle, or the client (or both).
Good luck, and please feel free to contact me with any questions. I can’t guarantee a speedy reply necessarily, but I will do my best.
Understanding the basics
What programming language does Ethereum use?
The programming language used in Ethereum development is Solidity. It is a contract-oriented programming language inspired by JavaScript, Python, and C++.
What is an oracle in blockchain?
A so-called blockchain oracle is a trusted data source that provides information about various states and occurrences that are used by smart contracts.
What is a smart contract oracle used for?
Smart contract oracles are used to provide a link between real-world events and digital contracts. This external data provided by oracles may (or may not) trigger smart contract executions.
John R. Kosinski
Chiang Mai, Thailand
Member since February 9, 2016
About the author
As a full-stack dev for nearly two decades, John’s worked with IoT, Blockchain, web, and mobile projects using C/C++, .NET, SQL, and JS.
Expertise
PREVIOUSLY AT