通过完成之前的教程,你已经展示了一个很好的掌握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 进入。
-
在右侧框中,运行
npm init -y
命令初始化新项目。 -
接下来,安装以下依赖项:
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: 调用其他合约
现在,我们将继续研究调用者智能合约,而不是直接跳转到预言机智能合约。这是为了帮助您了解从开始到结束的过程。
调用者智能合约所做的事情之一就是与预言机交互。让我们看看如何做到这一点。
为了使调用者智能合约与预言机交互,您必须向其提供以下信息:
- 预言机智能合约的地址
- 您要调用的函数的签名
我认为最简单的方法就是对预言机智能合约的地址进行硬编码。
但让我们戴上区块链开发者的帽子🎩并尝试弄清楚这是否是我们想要做的。
答案与区块链的工作原理有关。这意味着,一旦部署了合约,您就无法对其进行更新。正如当地人所说,合同是一成不变的。
如果您考虑一下,您会发现在很多情况下您都需要更新预言机的地址。举个例子,假设存在错误并且预言机被重新部署。然后怎样呢?你必须重新部署一切。并更新您的前端。
是的,这是昂贵的、耗时的,而且损害了用户体验😣。
因此,您想要实现的方法是编写一个简单的函数,将预言机智能合约的地址保存在变量中。然后,它实例化预言机智能合约,以便您的合约可以随时调用其函数
实战演练
在右侧的框中,我们为调用者合约粘贴了一个空壳。
-
声明一个
address
命名的oracleAddress
, 设置为private
,并且不赋值 -
接下来,创建一个名为 的函数
setOracleInstanceAddress
。该函数接受一个address
名为_oracleInstanceAddress
的参数。它是一个public函数,并且不返回任何内容。 -
第一行代码应设置
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
函数,您必须按照以下步骤操作:
FastFood
通过将以下代码片段粘贴到名为 的文件中来定义合约的接口FastFoodInterface.sol
:
pragma solidity 0.5.0;
interface FastFoodInterface {
function makeSandwich(string calldata _fillingA, string calldata _fillingB) external;
}
-
接下来,您必须将文件的内容导入./FastFoodInterface.sol到PrepareLunch合同中。
-
最后,您必须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
。
-
在声明pragma 的代码行之后,导入该
./EthPriceOracleInterface.sol
文件。 -
让我们添加一个名为
EthPriceOracleInterface
的private
变量oracleInstance
。将其放置在声明变量的代码行上方oracleAddress
。让我们成功吧。 -
现在让我们跳转到该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。
挺容易!现在轮到你付诸实践了😉。
实战演练
-
修改代码为import“openzeppelin-solidity/contracts/ownership/Ownable.sol”的内容
-
要使合约继承自Ownable,您必须is Ownable按如下方式附加到其定义:
contract MyContract is Ownable {
}
-
将onlyOwner修饰符的名称附加到函数定义的末尾setOracleInstanceAddress。
-
当您在这里时,您可能希望触发一个事件,以便每次预言机地址更改时前端都会收到通知。我们继续并宣布了一个名为 的事件
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。
- 创建一个名为 的函数updateEthPrice。它不接受任何参数,并且应该是一个public函数。
- 函数的第一行应该调用该oracleInstance.getLatestEthPrice函数。将返回值存储在被uint256调用的id.
- 接下来,将myRequests映射设置id为true。
- 函数的最后一行应该触发该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];
- 最后,您的函数应该触发一个事件,让前端知道价格已成功更新。
实战演练
我们已经声明了一个名为callback
的public
函数。它需要两个类型为uint256:_ethPrice
和 参数_id
。
-
在声明智能合约的行之后,创建一个名为
ethPrice
的uint256
变量。让它private,但不要将它分配给任何东西。 -
创建一个名为
PriceUpdatedEvent
的事件。它需要两个参数:ethPrice和id
。两个参数的类型都是uint256
。
接下来,让我们填写函数体callback
。
-
该函数应首先检查以确保
myRequests[_id]
是true
.使用require
语句来执行此操作。第一个参数应该是myRequests[_id]
,第二个参数应该是“This request is not in my pending list”。 -
接下来,将新的 ETH 价格(来自函数参数的价格)保存到
ethPrice
变量中。 -
然后应该调用该函数以从映射
myRequests
中delete删除当前id
值。 -
最后,让我们启动
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
调用此函数。
-
第一行代码应该用
require
来确保msg.sender
equalsoracleAddress
。如果没有,它应该抛出以下错误:“您无权调用此函数。” -
请记住我们之前的课程中,要执行函数的其余部分,您应该在修饰符中放置一个
_;
。不要忘记添加它。
代码更新
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 价格。为了实现这一点,它只需实现两个函数:getLatestEthPrice
和setLatestEthPrice
。
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
。
- 在合约的底部,定义一个名为的函数
getLatestEthPrice
,该函数返回uint256.它应该是一个public函数。 - 在第一行,使用
randNonce++
递增randNonce
。 - 计算 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
。
- 首先,您需要将此
id
的pendingRequests
映射改为true
。 - 让我们执行
GetLatestEthPriceEvent
。它应传递以下参数:msg.sender(地址)和 id(uint256)。 - 最后,函数应返回 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
中移除。
实战演练
- 创建一个名为
setLatestEthPrice
的public function
。该函数需要三个参数:ethPrice(一个 uint256)、_callerAddress(一个地址)和 _id(一个 uint256)
。别忘了,只有所有者才能调用它。 - 使用
require
检查pendingRequests[_id]
是否为true
。第二个参数应该是 “This request is not in my pending list.”。如果你不知道如何做到这一点,请重温第 6 章以加深记忆。 - 从
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
传递给它。 - 最后,您需要触发一个事件,通知前端价格已成功更新。
实战演练
- 让我们创建一个名为
callerContractInstance
的CallerContractInterface
。 - 使用调用者合约的地址初始化
callerContractInstance
,就像上面使用myContractInstance
时一样。注意,调用者合约的地址应来自函数参数。 - 运行
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 价格
将一切联系在一起的简易客户端。
在此之前,敬请期待,祝您编码愉快!