Lesson 3: 如何构建Oracle (How to Build an Oracle)

通过完成之前的教程,你已经展示了一个很好的掌握Solidity和JavaScript;你可能正在构建你的第一个dapp的路上。如果是这样,您可能已经注意到智能合约不能通过HTTP请求或类似的方式直接访问来自外部世界的数据。相反,智能合约通过一种叫做oracle的东西来获取数据。
本课是旨在展示如何构建oracle并与之交互的三节课中的第一节。
在前两节课中,我们将教你构建和交互最简单的oracle,它只允许一个用户(它的所有者)从Binance的公共API获取数据。

也就是说,我有一个问题要问您:为什么用户会信任您的oracle?🤔🤔🤔
简单的回答是,他们不会。至少在有社会信任之前,或者你想出一个去中心化的版本。因此,在第三课中,我们将向您展示如何使您的oracle更加分散。但是,现在,让我们从头开始。
是时候写一些代码了!

Chapter 1: 设置

在开始之前,让我们明确一点:这是一个中级课程,需要一些 JavaScript 和 Solidity 知识。

如果你是 Solidity 的新手,强烈建议你在开始本课之前先学习第一课。

如果你对JavaScript不熟悉,可以考虑在开始本课之前先在其他地方学习一下教程。

现在,假设您正在构建一个 DeFi dapp,并希望让用户能够提取价值一定美元的以太币。为了满足这一要求,您的智能合约(为简单起见,我们从现在起称其为 “调用者合约”)必须知道一个以太币值多少钱。

问题是:JavaScript 应用程序可以通过请求 Binance 公共 API(或任何其他公开提供价格反馈的服务)轻松获取此类信息。但是,智能合约无法直接从外部世界获取数据。相反,它需要依赖oracle来获取数据。

呼!乍一看,这听起来像是一件很复杂的事。但是,只要一步一个脚印,我们就能让你一帆风顺。

我知道,一张图片有时胜过千言万语,所以这里有一张简单的图来解释它是如何工作的

+-------------------------+
|    Data Sources         |
|  (DEXes, CEXes, etc.)   |
+-------------+-----------+
              |
              v
+-------------+-----------+
|      Aggregation        |
|   (Consensus Price)     |
+-------------+-----------+
              |
              v
+-------------+-----------+
|       Security          |
|  (Data Validation,      |
|   Cryptographic         |
|    Signatures, etc.)    |
+-------------+-----------+
              |
              v
+-------------+-----------+
|      Integration        |
| (Smart Contract Access  |
|       to Price Data)    |
+-------------+-----------+
              |
              v
+-------------+-----------+
|   Smart Contracts       |
| (DeFi Protocols, dApps) |
| (Use Price Data for     |
|      Various Purposes)  |
+-------------------------+

实战演练

打开终端窗口,进入你的项目目录。然后,创建一个名为 EthPriceOracle 的目录并 cd 进入。

  1. 在右侧框中,运行 npm init -y 命令初始化新项目。

  2. 接下来,安装以下依赖项:truffle、openzeppelin-solidity、loom-js、loom-truffle-provider、bn.js 和 axios

注意:您可以通过运行类似下面的命令来安装多个软件包:

npm i <package-a> <package-b> <package-c>

你会问,为什么需要这些软件包?继续往下看,事情就会变得更清楚了。

你将使用 Truffle 来编译智能合约并将其部署到 Loom Testnet,因此我们创建了两个基本的 Truffle 项目:

  • Oracle将存放在 oracle 目录中
mkdir oracle && cd oracle && npx truffle init && cd ..
✔ Preparing to download box
✔ Downloading
✔ cleaning up temporary files
✔ Setting up box
  • caller合约将保存在caller目录中
mkdir caller && cd caller && npx truffle init && cd ..
✔ Preparing to download box
✔ Downloading
✔ cleaning up temporary files
✔ Setting up box

如果一切顺利,您的目录结构应该如下所示:

tree -L 2 -I node_modules
.
├── caller
│   ├── contracts
│   ├── migrations
│   ├── test
│   └── truffle-config.js
├── oracle
│   ├── contracts
│   ├── migrations
│   ├── test
│   └── truffle-config.js
└── package.json

注:学习如何使用 Truffle 超出了本课的范围。如果你想了解更多,请查看我们自己的 "使用 Truffle 部署 DApps "课程。

代码更新

Chapter 2: 调用其他合约

现在,我们将继续研究调用者智能合约,而不是直接跳转到预言机智能合约。这是为了帮助您了解从开始到结束的过程。

调用者智能合约所做的事情之一就是与预言机交互。让我们看看如何做到这一点。

为了使调用者智能合约与预言机交互,您必须向其提供以下信息:

  • 预言机智能合约的地址
  • 您要调用的函数的签名
    我认为最简单的方法就是对预言机智能合约的地址进行硬编码。

但让我们戴上区块链开发者的帽子🎩并尝试弄清楚这是否是我们想要做的。

答案与区块链的工作原理有关。这意味着,一旦部署了合约,您就无法对其进行更新。正如当地人所说,合同是一成不变的。

如果您考虑一下,您会发现在很多情况下您都需要更新预言机的地址。举个例子,假设存在错误并且预言机被重新部署。然后怎样呢?你必须重新部署一切。并更新您的前端。

是的,这是昂贵的、耗时的,而且损害了用户体验😣。

因此,您想要实现的方法是编写一个简单的函数,将预言机智能合约的地址保存在变量中。然后,它实例化预言机智能合约,以便您的合约可以随时调用其函数

实战演练

在右侧的框中,我们为调用者合约粘贴了一个空壳。

  1. 声明一个address命名的oracleAddress, 设置为private,并且不赋值

  2. 接下来,创建一个名为 的函数setOracleInstanceAddress。该函数接受一个address名为_oracleInstanceAddress 的参数。它是一个public函数,并且不返回任何内容。

  3. 第一行代码应设置oracleAddress_oracleInstanceAddress.

您将在下一章中继续充实这个函数。

代码更新

caller/CallerContract.sol

pragma solidity 0.5.0;
contract CallerContract {
    // start here
    address private oracleAddress;
    function setOracleInstanceAddress(address _oracleInstanceAddress) public{
        oracleAddress = _oracleInstanceAddress;
    }

}

chapter3:调用其他合约 - 续

惊人的!现在您已将预言机的地址保存到变量中,让我们了解如何从不同的合约调用函数。

调用预言机合约

为了让调用者合约与预言机进行交互,您必须首先定义一个称为接口的东西。

接口在某种程度上类似于合约,但它们仅声明函数。换句话说,接口不能:

  • 定义状态变量,
  • 构造函数,
  • 或从其他合同继承。

您可以将接口视为 ABI。由于它们用于允许不同的合约相互交互,因此它们的所有功能都必须是external.

让我们看一个简单的例子。假设有一个名为FastFood如下的合约:

pragma solidity 0.5.0;

contract FastFood {
  function makeSandwich(string calldata _fillingA, string calldata _fillingB) external {
    //Make the sandwich
  }
}

这个非常简单的合约实现了“制作”三明治的功能。如果你知道合约的地址FastFood和签名makeSandwich,那么你就可以调用它。

注意:函数签名包括函数名称、参数列表和返回值。

继续我们的示例,假设您要编写一个名为PrepareLunch 的合约来调用该makeSandwich函数,并传递“切片火腿”和“腌制蔬菜”等成分列表。我不饿,但这听起来很诱人。

为了让PrepareLunch智能合约能够调用该makeSandwich函数,您必须按照以下步骤操作:

  1. FastFood通过将以下代码片段粘贴到名为 的文件中来定义合约的接口FastFoodInterface.sol
pragma solidity 0.5.0;

interface FastFoodInterface {
   function makeSandwich(string calldata _fillingA, string calldata _fillingB) external;
}
  1. 接下来,您必须将文件的内容导入./FastFoodInterface.sol到PrepareLunch合同中。

  2. 最后,您必须FastFood使用以下接口实例化合约:

fastFoodInstance = FastFoodInterface(_address);

此时,PrepareLunch智能合约就可以调用智能合约makeSandwich的函数了FastFood:

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

将它们放在一起,PrepareLunch合同如下所示:

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");
  }
}

现在,当您设置调用者合约以执行updateEthPrice预言机智能合约中的函数时,让我们使用上面的示例来获得灵感。

实战演练

我们已经创建了一个名为 caller/EthPriceOracleInterface.sol 的新文件并将其放入新选项卡中。仔细阅读一下。然后,让我们重新关注选项卡 caller/CallerContract.sol

  1. 在声明pragma 的代码行之后,导入该./EthPriceOracleInterface.sol 文件。

  2. 让我们添加一个名为EthPriceOracleInterfaceprivate变量 oracleInstance。将其放置在声明变量的代码行上方oracleAddress。让我们成功吧。

  3. 现在让我们跳转到该setOracleInstanceAddress函数。EthPriceOracle使用实例化合约EthPriceOracleInterface并将结果存储在oracleInstance变量中。如果您不记得执行此操作的语法,请检查上面的示例。但首先,尝试在不偷看的情况下进行操作。

哎呀!到目前为止,您可能对 Solidity 有足够的了解,可以发现这段代码中存在安全漏洞🤯。在下一章中,我们将解释如何使setOracleInstanceAddress函数安全。

代码更新

pragma solidity 0.5.0;
import "./EthPriceOracleInterface.sol";
//1. Import from the "./EthPriceOracleInterface.sol" file
contract CallerContract {
  // 2. Declare `EthPriceOracleInterface`
  EthPriceOracleInterface private oracleInstance;
  address private oracleAddress;
  function setOracleInstanceAddress (address _oracleInstanceAddress) public {
    oracleAddress = _oracleInstanceAddress;
    //3. Instantiate `EthPriceOracleInterface`
    oracleInstance = EthPriceOracleInterface(oracleAddress);
    
  }
}

Chapter 4:函数修饰符

您是否成功识别了上一章中的安全问题?

如果没有,让我帮助你回答:当你编写一个public函数时,任何人都可以执行它…并将预言机的地址设置为他们想要的任何内容。

但如何解决这个问题呢?

onlyOwner 函数修饰符

解决方案如下:您必须使用称为modifier.修饰符是改变函数行为的一段代码。例如,您可以在执行实际函数之前检查是否满足特定条件。

因此,修复此安全漏洞需要您执行以下步骤:

  • 导入 OpenZeppelinOwnable智能合约的内容。我们在之前的课程中介绍了 OpenZeppelin,如果需要的话,只需返回并刷新您的记忆即可。
  • 使合约继承自Ownable.
  • 更改函数的定义setOracleInstanceAddress,以便它使用onlyOwner修饰符。
    使用修饰符的方式如下:
contract MyContract {
  function doSomething public onlyMe {
    // do something
  }
}

在此示例中,onlyMe修饰符首先在函数内的代码之前执行doSomething。

挺容易!现在轮到你付诸实践了😉。

实战演练

  1. 修改代码为import“openzeppelin-solidity/contracts/ownership/Ownable.sol”的内容

  2. 要使合约继承自Ownable,您必须is Ownable按如下方式附加到其定义:

contract MyContract is Ownable {
}
  1. 将onlyOwner修饰符的名称附加到函数定义的末尾setOracleInstanceAddress。

  2. 当您在这里时,您可能希望触发一个事件,以便每次预言机地址更改时前端都会收到通知。我们继续并宣布了一个名为 的事件newOracleAddressEvent。函数的最后一行setOracleInstanceAddress应该发出newOracleAddressEvent。将其oracleAddress作为参数传递。

代码更新

pragma solidity 0.5.0;
import "./EthPriceOracleInterface.sol";
// 1. import the contents of "openzeppelin-solidity/contracts/ownership/Ownable.sol"
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
contract CallerContract is Ownable { // 2. Make the contract inherit from `Ownable`
    EthPriceOracleInterface private oracleInstance;
    address private oracleAddress;
    event newOracleAddressEvent(address oracleAddress);
    // 3. On the next line, add the `onlyOwner` modifier to the `setOracleInstanceAddress` function definition
    function setOracleInstanceAddress (address _oracleInstanceAddress) public onlyOwner {
      oracleAddress = _oracleInstanceAddress;
      oracleInstance = EthPriceOracleInterface(oracleAddress);
      // 4. Fire `newOracleAddressEvent`
      emit newOracleAddressEvent(oracleAddress);
    }
}

chapter5:使用映射来跟踪请求

太好了,您已经完成了该setOracleInstanceAddress功能!

现在,您的前端可以调用它来设置预言机的地址。

接下来我们看看ETH价格是如何更新的。

要启动 ETH 价格更新,智能合约应调用getLatestEthPrice预言机的函数。现在,由于其异步特性,该getLatestEthPrice函数无法返回这一点信息。相反,它返回的内容id对于每个请求都是唯一的。然后,预言机继续从 Binance API 获取 ETH 价格,并执行callback调用者合约公开的函数。最后,该callback函数更新调用者合约中的 ETH 价格。

这是非常重要的一点,所以在继续之前花几分钟思考一下。

现在,实施这个听起来是一个难题吗?事实上,它的工作方式非常简单,会让您大吃一惊。请耐心等待我接下来的两章🤓。

映射

您的 dapp 的每个用户都可以发起一项操作,要求调用者合约发出更新 ETH 价格的请求。由于调用者无法控制何时获得响应,因此您必须找到一种方法来跟踪这些待处理的请求。这样做,您将能够确保对该callback函数的每次调用都与合法请求相关联。

为了跟踪请求,您将使用名为myRequests 的映射。在 Solidity 中,映射基本上是一个哈希表,其中存在所有可能的键。但有一个问题。最初,每个值都使用类型的默认值进行初始化。

您可以使用类似以下内容定义映射:

mapping(address => uint) public balances;

你能猜出这个片段的作用吗?嗯…正如所说,它设定了所有可能的addresses余额为0。为什么是0?因为这是 的默认值uint

msg.sender设置to的余额someNewValue非常简单:

balances[msg.sender] = someNewValue

实战演练

我们已经为您声明了映射myRequests。键是 an uint256,值是 a bool。我们还声明了一个名为 的事件ReceivedNewRequestIdEvent。

  1. 创建一个名为 的函数updateEthPrice。它不接受任何参数,并且应该是一个public函数。
  2. 函数的第一行应该调用该oracleInstance.getLatestEthPrice函数。将返回值存储在被uint256调用的id.
  3. 接下来,将myRequests映射设置id为true。
  4. 函数的最后一行应该触发该ReceivedNewRequestIdEvent事件。将其id作为参数传递。

代码更新

pragma solidity 0.5.0;
import "./EthPriceOracleInterface.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
contract CallerContract is Ownable {
    EthPriceOracleInterface private oracleInstance;
    address private oracleAddress;
    mapping(uint256=>bool) myRequests;
    event newOracleAddressEvent(address oracleAddress);
    event ReceivedNewRequestIdEvent(uint256 id);
    function setOracleInstanceAddress (address _oracleInstanceAddress) public onlyOwner {
      oracleAddress = _oracleInstanceAddress;
      oracleInstance = EthPriceOracleInterface(oracleAddress);
      emit newOracleAddressEvent(oracleAddress);
    }
    // Define the `updateEthPrice` function
    function updateEthPrice( )public {
        uint256 id = oracleInstance.getLatestEthPrice();
        myRequests[id] = true;
        emit ReceivedNewRequestIdEvent(id);

    }
}

chapter6:回调函数

调用者合约逻辑已基本完成,但还有一件事您应该注意。

正如上一章提到的,调用Binance公共API是一个异步操作。因此,调用方智能合约必须提供callback预言机应在稍后(即获取 ETH 价格时)调用的函数。

callback函数的工作原理如下:

  • 首先,您需要确保只能针对有效的id.为此,您将使用一个require语句。

简而言之,require如果条件为 ,则语句将引发错误并停止函数的执行false。

我们看一下Solidity官方文档中的一个例子:

require(msg.sender == chairperson, "Only chairperson can give right to vote.");

第一个参数的计算结果为true or false。如果是false,函数执行将停止,智能合约将抛出错误——“只有主席才能赋予投票权。”

  • 一旦您知道它id是有效的,您就可以继续将其从myRequests映射中删除。

注意:要从映射中删除元素,您可以使用如下所示的内容:delete myMapping[key];

  • 最后,您的函数应该触发一个事件,让前端知道价格已成功更新。

实战演练

我们已经声明了一个名为callbackpublic函数。它需要两个类型为uint256:_ethPrice和 参数_id

  1. 在声明智能合约的行之后,创建一个名为 ethPriceuint256变量。让它private,但不要将它分配给任何东西。

  2. 创建一个名为PriceUpdatedEvent 的事件。它需要两个参数:ethPrice和id。两个参数的类型都是uint256

接下来,让我们填写函数体callback

  1. 该函数应首先检查以确保myRequests[_id]true.使用require语句来执行此操作。第一个参数应该是myRequests[_id],第二个参数应该是“This request is not in my pending list”。

  2. 接下来,将新的 ETH 价格(来自函数参数的价格)保存到ethPrice变量中。

  3. 然后应该调用该函数以从映射myRequests中delete删除当前id值。

  4. 最后,让我们启动PriceUpdatedEvent.参数应该来自函数参数。

这需要实现很多逻辑,但仍然遗漏了一些东西。你能猜出是什么吗?

在进入下一章之前先想一想。

代码更新

pragma solidity 0.5.0;
  import "./EthPriceOracleInterface.sol";
  import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
  contract CallerContract is Ownable {
      // 1. Declare ethPrice
      uint256 private ethPrice;
      EthPriceOracleInterface private oracleInstance;
      address private oracleAddress;
      mapping(uint256=>bool) myRequests;
      event newOracleAddressEvent(address oracleAddress);
      event ReceivedNewRequestIdEvent(uint256 id);
      // 2. Declare PriceUpdatedEvent
      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 {
        // 3. Continue here
        require(myRequests[_id], "This request is not in my pending list.");
        ethPrice = _ethPrice;
        delete myRequests[_id];
        emit PriceUpdatedEvent(_ethPrice, _id);
      }
  }

chapter7:唯一的 Oracle 修饰符

你找到答案了吗?

在包装callback函数之前,必须确保只有预言机合约才允许调用它。

在本章中,您将创建一个修饰符来阻止其他合约调用您的callback函数。

注意:我们不会深入研究修饰符的工作原理。如果细节模糊,请继续查看我们之前的课程。

请记住,您已经将预言机的地址存储到名为oracleAddress 的变量中。因此,修饰符应该只检查调用该函数的地址是否为oracleAddress

但是我怎么知道调用你问的函数的地址呢?

在Solidity中,msg.sender是一个特殊变量,用于指定消息的发送者。换句话说,您可以使用msg.sender来找出调用您的函数的地址。

实战演练

我们已经定义了一个名为 onlyOracle的修饰符并将其附加到该 callback函数。让我们填写修饰符的主体,以便仅允许oracle 调用此函数。

  1. 第一行代码应该用require来确保msg.sender equals oracleAddress。如果没有,它应该抛出以下错误:“您无权调用此函数。”

  2. 请记住我们之前的课程中,要执行函数的其余部分,您应该在修饰符中放置一个_;。不要忘记添加它。

代码更新

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() {
      // Start here
      require(msg.sender == oracleAddress,  "You are not authorized to call this function.");
      _;
    }

}

Chapter 8: getLatestEthPrice 函数

做得好!您刚刚完成了调用者智能合约的实施💪🏻💪🏻💪🏻。

现在是时候转向预言机合约了。让我们首先看看这个合约应该做什么。

其要点是,预言机合约充当桥梁,使调用者合约能够访问 ETH 价格。为了实现这一点,它只需实现两个函数:getLatestEthPricesetLatestEthPrice

getLatestEthPrice 函数

为了允许调用者跟踪他们的请求,该getLatestEthPrice函数应该首先计算请求id,并且出于安全原因,这个数字应该很难猜测。

您问什么安全原因?

在第三课中,您将使预言机更加去中心化。生成唯一的 ID 使预言机更难串通并操纵特定请求的价格。

换句话说,您想要生成一个随机数。

但是如何在 Solidity 中生成随机数呢?

一种解决方案是在键盘上释放僵尸。但是可怜的僵尸也会输入空格和字母,所以你的“随机数”最终会看起来像这样:erkljf3r4398r4390r830

因此,即使在本课程的制作过程中没有僵尸受伤,生成随机数的解决方案也根本不够好😎。

然而,在 Solidity 中,您可以使用如下keccak256函数计算“足够好”的随机数:

uint randNonce = 0;
uint modulus = 1000;
uint randomNumber = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;

上面的代码采用时间戳now、msg.sender和递增nonce(一个仅使用一次的数字,因此我们不会使用相同的输入参数运行相同的哈希函数两次)。然后它打包输入并用于keccak256将它们转换为随机散列。接下来,它将哈希值转换为uint.最后,它通常% modulus只取最后 3 位数字。这会给你一个 0 到 之间的“足够好”的随机数modulus。

第 4 课解释了为什么这种方法不是 100% 安全,并提供了一些生成真正安全的随机数的替代方法。学完本课后请读一读。

实战演练

我们已经为EthPriceOracle合约创建了一个外壳。在继续之前快速浏览一下代码。请注意,您的合同已包含Ownable,我们也已包含该文件的内容CallerContractInterface.sol

  1. 在合约的底部,定义一个名为的函数getLatestEthPrice,该函数返回uint256.它应该是一个public函数。
  2. 在第一行,使用randNonce++递增randNonce
  3. 计算 0 和 之间的随机数modulus,并将结果存储在被uint调用的id.如果您遇到困难,请随意查看上面我们生成随机数的示例😉。您的解决方案应该类似getLatestEthPrice.您将在下一章中继续充实该函数。

代码更新

CallerContractInterface.sol

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);
  // Start here
  function getLatestEthPrice() public returns(uint256){
    randNonce++;
    uint id = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % modulus;
  }
}

Chapter 9: getLatestEthPrice 函数-续

干得好,你已经计算出了请求 ID。

接下来,你需要实现一个简单的系统来跟踪待处理的请求。就像你为调用者合约所做的那样,你将使用一个映射来实现它。这次我们把它叫做 pendingRequests

getLatestEthPrice 函数也应该触发一个事件,最后,它应该返回请求 id

让我们看看如何在代码中实现这一点。

实战演练

我们已经定义了一个名为 pendingRequests 的映射。键是 uint(随机数),值是 bool

  1. 首先,您需要将此 idpendingRequests 映射改为 true
  2. 让我们执行 GetLatestEthPriceEvent。它应传递以下参数:msg.sender(地址)和 id(uint256)。
  3. 最后,函数应返回 id。

代码更新

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;

    // Start here
    pendingRequests[id] = true;
    emit GetLatestEthPriceEvent(msg.sender, id);
    return id;
  }
}

Chapter 10: The setLatestEthPrice Function

真棒.在本章中,你将把到目前为止学到的知识综合起来,编写 setLatestEthPrice 函数。这将会有点复杂,但没什么可怕的。我将避免大的思维跳跃,并确保每一步都得到很好的解释。

oracle 的 JavaScript 组件(我们将在下一课编写的组件)从 Binance 的公共 API 获取 ETH 价格,然后调用setLatestEthPrice,并传递以下参数:

  • ETH 价格、
  • 发起请求的合约地址
  • 请求的 id
    首先,您的函数必须确保只有所有者才能调用该函数。然后,与第 6 章中编写的代码类似,函数应检查请求id是否有效。如果有效,则应将其从 pendingRequests 中移除。

实战演练

  1. 创建一个名为 setLatestEthPricepublic function。该函数需要三个参数:ethPrice(一个 uint256)、_callerAddress(一个地址)和 _id(一个 uint256)。别忘了,只有所有者才能调用它。
  2. 使用require检查 pendingRequests[_id] 是否为 true。第二个参数应该是 “This request is not in my pending list.”。如果你不知道如何做到这一点,请重温第 6 章以加深记忆。
  3. pendingRequests映射中移除 id。如果你卡住了,下面是如何从映射中移除键的方法:
delete myMapping[myId];

我们将在下一章继续定义 setLatestEthPrice 函数。

代码更新

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;
  }
  // Start here
  function setLatestEthPrice(uint256 _ethPrice, address _callerAddress, uint256 _id) public onlyOwner{
    require(pendingRequests[_id],"This request is not in my pending list.");
    delete pendingRequests[_id];
  }
}

Chapter 11: The Oracle Contract

setLatestEthPrice 函数已基本完成。接下来,您必须

实例化 CallerContractInstance。如果您忘记了如何操作或需要一点灵感,请快速浏览下面的示例:

MyContractInterface myContractInstance;
myContractInstance = MyContractInterface(contractAddress)
  • 调用者合约实例化后,现在可以执行其callback方法,并将新的 ETH 价格和请求的 id 传递给它。
  • 最后,您需要触发一个事件,通知前端价格已成功更新。

实战演练

  1. 让我们创建一个名为 callerContractInstance CallerContractInterface
  2. 使用调用者合约的地址初始化 callerContractInstance,就像上面使用 myContractInstance 时一样。注意,调用者合约的地址应来自函数参数。
  3. 运行callerContractInstance.callback函数,传入 _ethPrice_id
    最后,发出 SetLatestEthPriceEvent。它需要两个参数:_ethPrice_callerAddress
    拍拍自己的背,你刚刚完成了Oracle智能合约的编写!

代码更新

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];
    // Start here
    CallerContractInterface callerContractInstance;
    callerContractInstance = CallerContractInterface(_callerAddress);
    callerContractInstance.callback(_ethPrice, _id);
    emit SetLatestEthPriceEvent(_ethPrice, _callerAddress);
  }
}

Ending

恭喜你,你走到了最后!

在本课中,你将通过编写两个简单的智能合约,开始探索如何构建集中式Oracle并与之交互。

在下一课中,我们将通过实现来拼凑缺失的部分:

Oracle的 JavaScript 组件。从 Binance 公共 API 获取 ETH 价格
将一切联系在一起的简易客户端。
在此之前,敬请期待,祝您编码愉快!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值