Skip to content

Latest commit

 

History

History
211 lines (184 loc) · 10 KB

01.md

File metadata and controls

211 lines (184 loc) · 10 KB
title actions requireLogin material
Getting Set Up
checkAnswer
hints
true
editor
language startingCode answer
JavaScript
EthPriceOracle.js utils/common.js oracle/EthPriceOracle.sol caller/CallerContract.sol caller/EthPriceOracleInterface.sol oracle/CallerContractInterface.sol
const axios = require('axios') const BN = require('bn.js') const common = require('./utils/common.js') const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000 const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key' const CHUNK_SIZE = process.env.CHUNK_SIZE || 3 const MAX_RETRIES = process.env.MAX_RETRIES || 5 const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json') var pendingRequests = [] // Start here
const fs = require('fs') const Web3 = require('web3') const { Client, NonceTxMiddleware, SignedTxMiddleware, LocalAddress, CryptoUtils, LoomProvider } = require('loom-js') function loadAccount (privateKeyFileName) { const extdevChainId = 'extdev-plasma-us1' const privateKeyStr = fs.readFileSync(privateKeyFileName, 'utf-8') const privateKey = CryptoUtils.B64ToUint8Array(privateKeyStr) const publicKey = CryptoUtils.publicKeyFromPrivateKey(privateKey) const client = new Client( extdevChainId, 'wss://extdev-plasma-us1.dappchains.com/websocket', 'wss://extdev-plasma-us1.dappchains.com/queryws' ) client.txMiddleware = [ new NonceTxMiddleware(publicKey, client), new SignedTxMiddleware(privateKey) ] client.on('error', msg => { console.error('Connection error', msg) }) return { ownerAddress: LocalAddress.fromPublicKey(publicKey).toString(), web3js: new Web3(new LoomProvider(client, privateKey)), client } } module.exports = { loadAccount, };
pragma solidity 0.5.0; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "./CallerContractInterface.sol"; contract EthPriceOracle is Ownable { uint private randNonce = 0; uint private modulus = 1000; mapping(uint256=>bool) pendingRequests; event GetLatestEthPriceEvent(address callerAddress, uint id); event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress); function getLatestEthPrice() public returns (uint256) { randNonce++; uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus; pendingRequests[id] = true; emit GetLatestEthPriceEvent(msg.sender, id); return id; } function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner { require(pendingRequests[_id], "This request is not in my pending list."); delete pendingRequests[_id]; CallerContractInterface callerContractInstance; callerContractInstance = CallerContractInterface(_callerAddress); callerContractInstance.callback(_ethPrice, _id); emit SetLatestEthPriceEvent(_ethPrice, _callerAddress); } }
pragma solidity 0.5.0; import "./EthPriceOracleInterface.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract CallerContract is Ownable { uint256 private ethPrice; EthPriceOracleInterface private oracleInstance; address private oracleAddress; mapping(uint256=>bool) myRequests; event newOracleAddressEvent(address oracleAddress); event ReceivedNewRequestIdEvent(uint256 id); event PriceUpdatedEvent(uint256 ethPrice, uint256 id); function setOracleInstanceAddress (address _oracleInstanceAddress) public onlyOwner { oracleAddress = _oracleInstanceAddress; oracleInstance = EthPriceOracleInterface(oracleAddress); emit newOracleAddressEvent(oracleAddress); } function updateEthPrice() public { uint256 id = oracleInstance.getLatestEthPrice(); myRequests[id] = true; emit ReceivedNewRequestIdEvent(id); } function callback(uint256 _ethPrice, uint256 _id) public onlyOracle { require(myRequests[_id], "This request is not in my pending list."); ethPrice = _ethPrice; delete myRequests[_id]; emit PriceUpdatedEvent(_ethPrice, _id); } modifier onlyOracle() { require(msg.sender == oracleAddress, "You are not authorized to call this function."); _; } }
pragma solidity 0.5.0; contract EthPriceOracleInterface { function getLatestEthPrice() public returns (uint256); }
pragma solidity 0.5.0; contract CallerContractInterface { function callback(uint256 _ethPrice, uint256 id) public; }
const axios = require('axios') const BN = require('bn.js') const common = require('./utils/common.js') const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000 const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key' const CHUNK_SIZE = process.env.CHUNK_SIZE || 3 const MAX_RETRIES = process.env.MAX_RETRIES || 5 const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json') var pendingRequests = [] async function getOracleContract (web3js) { const networkId = await web3js.eth.net.getId() return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address) }

In this lesson, you're going to create the JavaScript component of the oracle that fetches the ETH price from the Binance API. Then, you'll build a basic client that interacts with the oracle.

Take a look at the screen to the right. To get everything ready for you, we've:

  • Created a new JavaScript file called EthPriceOracle.js
  • Imported a bunch of prerequisites
  • Initialized a few variables
  • Filled in some boilerplate code that connects your app to Extdev Testnet (refer to the utils/common.js tab for details).

Things to note:

  • We've imported the build artifacts, and stored them in a const called OracleJSON. If you don't recall from the previous lessons what the build artifacts are, here's a quick refresher. The build artifacts are comprised of the bytecode versions of the smart contracts, ABIs, and some internal data Truffle is using to correctly deploy the code.

    What is an ABI you ask?

    In a nutshell, an ABI describes the interface between two computer programs. Don't mistake this for an API that defines an interface at a higher level (source code). An ABI describes how functions can be called and how data is stored in a machine-readable format. As an example, here's how the pendingRequests property of the oracle contract is described:

    {
      "constant": false,
      "id": 143,
      "name": "pendingRequests",
      "nodeType": "VariableDeclaration",
      "scope": 240,
      "src": "229:38:2",
      "stateVariable": true,
      "storageLocation": "default",
      "typeDescriptions": {
        "typeIdentifier": "t_mapping$_t_uint256_$_t_bool_$",
        "typeString": "mapping(uint256 => bool)"
      },

    Aren’t you happy to be using higher-level languages such as JavaScript and Solidity that abstract away all this complexity? I certainly am🤓!

  • We've initialized an empty array called pendingRequests. You'll use this later to keep track of the incoming requests.

☞ Spend a couple of minutes to give the code a read-through before moving on!

Instantiate the Oracle Contract

The build artifacts live inside of a JSON file, and we've imported them using the following line of code:

const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')

As an example, based on the information stored in this file, your application knows that the setLatestEthPrice function takes three uint256s as arguments (_ethPrice, _callerAddress, and _id), and it can create a transaction that calls this function.

But before that, interacting with a deployed contract from JavaScript requires you to instantiate it using the web3.eth.Contract. Let's look at an example to make the concept clear:

const myContract = new web3js.eth.Contract(myContractJSON.abi, myContractJSON.networks[networkId].address)

Note that the above example uses a variable called networkId that identifies the network on which your contract has been deployed. The networkId for Extdev is 9545242630824, so you could declare the networkId variable as follows:

const networkId = 9545242630824

Easy peasy! But no matter how simple the above line of code looks, it's not such a good idea to hardcode the identifier of the network like this. Why not? Well, because doing so would require you to update networkId every time your contract gets deployed to a different network.

A better solution is to resolve networkId by calling the web3js.eth.net.getId() function:

const networkId = await web3js.eth.net.getId()

Put It to the Test

  1. Create an async function named getOracleContract. It takes one parameter: web3js.
  2. First, let's store the result of web3js.eth.net.getId into a const called networkId, so you can use this in the next line. Keep in mind that getId is an async function, meaning that it returns a promise. To call this function, you must prepend await. This way, the code stops until the promise resolves.
  3. The second line of code should return an instance of your contract. As already mentioned, we've imported the build artifacts and saved them into a const called OracleJSON. Other than this, your code should be similar to the example above.