Skip to content

Commit

Permalink
Add latest lessons (CryptozombiesHQ#453)
Browse files Browse the repository at this point in the history
Add latest lessons
  • Loading branch information
andreipope authored Nov 3, 2020
1 parent 552fbb1 commit 7986a7d
Show file tree
Hide file tree
Showing 73 changed files with 12,651 additions and 418 deletions.
18 changes: 18 additions & 0 deletions en/14/00-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: How to Build an Oracle
header: How to Build an Oracle
path: solidity_advanced
publishedOn: Cryptozombies
---

By completing the previous tutorials you've demonstrated a good grasp of Solidity and JavaScript; and you are probably well on your way to building your first dapp. If so, you may already have noticed that smart contracts can't directly access data from the outside world through an HTTP request or something similar. Instead, smart contracts pull data through something called an **_oracle_**.

This lesson is the first in the sequence of three lessons that aim to show how you can **_build and interact with an oracle_**.

In the first two lessons, we will be teaching you to build and interact with the simplest possible oracle that allows only one user, its owner, to fetch data from Binance's public API.

That said, I have a question for you: why would users trust your oracle?🤔🤔🤔

The quick answer is that they wouldn't. At least not until there **_social trust_** or you come up with a **_decentralized version_**. Thus, in the third lesson, we'll show you how to make your oracle more decentralized. But, for now, let's start with the beginning.

Time to write some code!
128 changes: 128 additions & 0 deletions en/14/01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: Settings Things Up
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
terminal:
prompt: "$ "
help:
You should probably run the `npm init -y` command followed by the `npm i truffle openzeppelin-solidity loom-js loom-truffle-provider bn.js axios` command.
commands:
"npm init -y":
hint: npm init -y
output: |
Wrote to /Users/CZ/Documents/EthPriceOracle/package.json:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
"npm i truffle openzeppelin-solidity loom-js loom-truffle-provider bn.js axios":
hint: npm i truffle openzeppelin-solidity loom-js loom-truffle-provider bn.js axios
output: |
+ [email protected]
+ [email protected]
+ [email protected]
+ [email protected]
+ [email protected]
+ [email protected]
added 416 packages from 690 contributors and audited 175192 packages in 111.298s
found 2 low severity vulnerabilities
run `npm audit fix` to fix them, or `npm audit` for details
---

Before we begin, let's be clear: this is an intermediate lesson, and it requires a bit of **_JavaScript_** and **_Solidity_** knowledge.

If you're new to Solidity, it's highly recommended that you go over the first lessons before starting this one.

If you are not comfortable with **JavaScript**, consider going through a tutorial elsewhere before starting this lesson.

---

Now, let's suppose you're building a DeFi dapp, and want to give your users the ability to withdraw ETH worth a certain amount of USD. To fulfill this request, your smart contract (for simplicity's sake we'll call it the "caller contract" from here onwards) must know how much one Ether is worth.

And here's the thing: a JavaScript application can easily fetch this kind of information, making requests to the Binance public API (or any other service that publicly provides a price feed). But, a smart contract can't directly access data from the outside world. Instead, it relies on an **_oracle_** to pull the data.

Phew! At first glance, this sounds like a complicated thing to do 🤯. But, by taking it one step at a time, we'll set you on a smooth sail.

Now I know that a picture is sometimes worth a thousand words, so here's a simple diagram that explains how this works:

<img src="/course/static/image/lesson-14/EthPriceOracleOverview.png" alt="Eth Price Oracle Overview" width="469">

Let this sink in before you read on.

For now, let's initialize your new project.

## Put It to the Test

Fire up a terminal window and move into your projects directory. Then, create a directory called `EthPriceOracle` and `cd` into it.

1. In the box to the right, initialize your new project by running the `npm init -y` command.

2. Next, let's install the following dependencies: `truffle`, `openzeppelin-solidity`, `loom-js`, `loom-truffle-provider`, `bn.js`, and `axios`.

>Note: You can install multiple packages by running something like the following:
```bash
npm i <package-a> <package-b> <package-c>
```

Why do you need all these packages you ask? Read on and things will become clearer.

You'll be using Truffle to compile and deploy your smart contracts to Loom Testnet so we've gone ahead and created two bare-bones Truffle projects:

* The oracle will live in the `oracle` directory:

```bash
mkdir oracle && cd oracle && npx truffle init && cd ..
```

```
✔ Preparing to download box
✔ Downloading
✔ cleaning up temporary files
✔ Setting up box
```

* The caller contract will live in the `caller` directory:

```bash
mkdir caller && cd caller && npx truffle init && cd ..
```

```
✔ Preparing to download box
✔ Downloading
✔ cleaning up temporary files
✔ Setting up box
```

We trust you to do the same and, if everything goes well, your directory structure should look something like the following:

```bash
tree -L 2 -I node_modules
```

```
.
├── caller
│   ├── contracts
│   ├── migrations
│   ├── test
│   └── truffle-config.js
├── oracle
│   ├── contracts
│   ├── migrations
│   ├── test
│   └── truffle-config.js
└── package.json
```

>Note: Learning how to use Truffle is beyond the scope of this lesson. If you're inclined to learn more, check out our very own <a href="https://cryptozombies.io/en/lesson/10" target=_blank>Deploying DApps with Truffle</a> lesson.
55 changes: 55 additions & 0 deletions en/14/02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Calling Other Contracts
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
editor:
language: sol
startingCode:
"caller/CallerContract.sol": |
pragma solidity 0.5.0;
contract CallerContract {
// start here
}
answer: |
pragma solidity 0.5.0;
contract CallerContract {
address private oracleAddress;
function setOracleInstanceAddress (address _oracleInstanceAddress) public {
oracleAddress = _oracleInstanceAddress;
}
}
---

Now, instead of jumping directly to the oracle smart contract, we'll continue by looking into the caller smart contract. This is to help you understand the process from start to finish.

One of the things the caller smart contract does is to interact with the oracle. Let's see how you can do this.

For the caller smart contract to interact with the oracle, you must provide it with the following bits of information:

* The address of the oracle smart contract
* The signature of the function you want to call

I reckon that the simplest approach would be to just hardcode the address of the oracle smart contract.

But let’s put on our blockchain developer hat🎩and try to figure out if this is what we want to do.

The answer has to do with how the blockchains work. Meaning that, once a contract is deployed, there's no way you can update it. As the natives call it, **_contracts are immutable_**.

If you think about it, you'll see that there are plenty of cases in which you would want to update the address of the oracle. As an example, say there's a bug and the oracle gets redeployed. What then? You'll have to redeploy everything. And update your front-end.

Yeah, this is costly, time-consuming, and it harms the user experience😣.

So the way you'd want to go about this is to write a simple function that saves the address of the oracle smart contract in a variable. Then, it instantiates the oracle smart contract so your contract can call its functions at any time.

## Put It to the Test

In the box to the right, we've pasted an empty shell for the caller contract.

1. Declare an `address` named `oracleAddress`. Make it `private`, and don't assign it to anything.

2. Next, create a function called `setOracleInstanceAddress`. This function takes an `address` argument named `_oracleInstanceAddress`. It's a `public` function, and it doesn't return anything.

3. The first line of code should set `oracleAddress` to `_address`.

You'll continue fleshing out this function in the next chapter.
125 changes: 125 additions & 0 deletions en/14/03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
title: Calling Other Contracts- Cont'd
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
editor:
language: sol
startingCode:
"caller/CallerContract.sol": |
pragma solidity 0.5.0;
//1. Import from the "./EthPriceOracleInterface.sol" file
contract CallerContract {
// 2. Declare `EthPriceOracleInterface`
address private oracleAddress;
function setOracleInstanceAddress (address _oracleInstanceAddress) public {
oracleAddress = _oracleInstanceAddress;
//3. Instantiate `EthPriceOracleInterface`
}
}
"caller/EthPriceOracleInterface.sol": |
pragma solidity 0.5.0;
contract EthPriceOracleInterface {
function getLatestEthPrice() public returns (uint256);
}
answer: |
pragma solidity 0.5.0;
import "./EthPriceOracleInterface.sol";
contract CallerContract {
EthPriceOracleInterface private oracleInstance;
address private oracleAddress;
function setOracleInstanceAddress (address _oracleInstanceAddress) public {
oracleAddress = _oracleInstanceAddress;
oracleInstance = EthPriceOracleInterface(oracleAddress);
}
}
---

Awesome! Now that you've saved the address of the oracle into a variable, let's learn about how you can call a function from a different contract.

## Calling the Oracle Contract

For the caller contract to interact with the oracle, you must first define something called an **_interface_**.

Interfaces are somehow similar to contracts, but they only declare functions. In other words, an interface can't:
* define state variables,
* constructors,
* or inherit from other contracts.

You can think of an interface as of an ABI. Since they're used to allow different contracts to interact with each other, all functions must be `external`

Let's look at a simple example. Suppose there's a contract called `FastFood` that looks something like the following:

```sol
pragma solidity 0.5.0;
contract FastFood {
function makeSandwich(string calldata _fillingA, string calldata _fillingB) external {
//Make the sandwich
}
}
```

This very simple contract implements a function that "makes" a sandwich. If you know the address of the `FastFood` contract and the signature of the `makeSandwich`, then you can call it.

>Note: A function signature comprises the function name, the list of the parameters, and the return value(s).
Continuing with our example, let's say you want to write a contract called `PrepareLunch` that calls the `makeSandwich` function, passing the list of ingredients such as "sliced ham" and "pickled veggies". I'm not hungry but this sounds tempting😄.

To make it so that the `PrepareLunch` smart contract can call the `makeSandwich` function, you must follow the following steps:

1. Define the interface of the `FastFood` contract by pasting the following snippet into a file called `FastFoodInterface.sol`:

```solidity
pragma solidity 0.5.0;
interface FastFoodInterface {
function makeSandwich(string calldata _fillingA, string calldata _fillingB) external;
}
```

2. Next, you must import the contents of the `./FastFoodInterface.sol` file into the `PrepareLaunch` contract.

3. Lastly, you must instantiate the `FastFood` contract using the interface:

```solidity
fastFoodInstance = FastFoodInterface(_address);
```

At this point, the `PrepareLunch` smart contract can call the `makeSandwich` function of the `FastFood` smart contract:

```solidity
fastFoodInstance.makeSandwich("sliced ham", "pickled veggies");
```

Putting it together, here's how the `PrepareLunch` contract would look like:

```sol
pragma solidity 0.5.0;
import "./FastFoodInterface.sol";
contract PrepareLunch {
FastFoodInterface private fastFoodInstance;
function instantiateFastFoodContract (address _address) public {
fastFoodInstance = FastFoodInterface(_address);
fastFoodInstance.makeSandwich("sliced ham", "pickled veggies");
}
}
```

Now, let's use the above example for inspiration as you set up the caller contract to execute the `updateEthPrice` function from the oracle smart contract.

## Put It to the Test

We've gone ahead and created a new file called `caller/EthPriceOracleInterface.sol` and placed it into a new tab. Give it a read-through. Then, let's focus back on the `caller/CallerContract.sol` tab.

1. After the line of code that declares the `pragma`, import the `./EthPriceOracleInterface.sol` file.

2. Let's add an `EthPriceOracleInterface` variable named `oracleInstance`. Place it above the line of code that declares the `oracleAddress` variable. Let's make it `private`.

3. Now let's jump to the `setOracleInstanceAddress` function. Instantiate the `EthPriceOracle` contract using `EthPriceOracleInterface` and store the result in the `oracleInstance` variable. If you can't remember the syntax for doing this, check the example from above. But first, try to do it without peeking.

Oops! By now, you probably know enough about Solidity to figure out that there's a security hole in this code🤯. In the next chapter, we'll explain how to make the `setOracleInstanceAddress` function safe.
Loading

0 comments on commit 7986a7d

Please sign in to comment.