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.

Oracle contracts process illustration

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.

Install Truffle

Truffle is the main thing we’re going to use for development, and absolutely is a requirement for this guide. Find and follow the specific instructions for your OS to install Truffle. Below are some links that will hopefully help you.

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.

https://github.com/trufflesuite/ganache-cli

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/client/migrations/2_deploy_contracts.js

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:

Diagram of ethereum oracle contract processes

  • 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++.

About the author

John R. Kosinski, Thailand
member since October 28, 2015
As a veteran full-stack developer, John's great breadth and depth of experience include cryptocurrency, IoT, Blockchain, and mobile projects. His foundation is in C and C++ with many years of experience in .NET. His work experience up until 2009 was in the NYC and NYC area; since 2009, he's been living abroad and working remotely. Currently, finishing up a brief hiatus to study Blockchain and smart contract development. [click to continue...]
Hiring? Meet the Top 10 Freelance Ethereum Developers for Hire in November 2018

Comments

comments powered by Disqus
Subscribe
Free email updates
Get the latest content first.
No spam. Just great articles & insights.
Free email updates
Get the latest content first.
Thank you for subscribing!
Check your inbox to confirm subscription. You'll start receiving posts after you confirm.
Trending articles
Relevant Technologies
About the author
John R. Kosinski
C# Developer
As a veteran full-stack developer, John's great breadth and depth of experience include cryptocurrency, IoT, Blockchain, and mobile projects. His foundation is in C and C++ with many years of experience in .NET. His work experience up until 2009 was in the NYC and NYC area; since 2009, he's been living abroad and working remotely. Currently, finishing up a brief hiatus to study Blockchain and smart contract development.