forked from CryptozombiesHQ/cryptozombie-lessons
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add latest lessons (CryptozombiesHQ#453)
Add latest lessons
- Loading branch information
1 parent
552fbb1
commit 7986a7d
Showing
73 changed files
with
12,651 additions
and
418 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.