从零开始写预言机(一)

本博客整理于 cryptozombies.io/zh/lesson
查看原教程请参考如上链接,本博客做个人记录!

从零开始写预言机

一、什么是预言机?

网上相关的解释很多,我想说说我的理解。

以太坊的本质就是一个由交易触发的状态机,任何在以太坊上面状态的改变都需要在链外发起相关的(Transaction)交易来实现,区块链无法主动获取区块链以外的数据。而预言机就是一个将区块链下的数据传输至区块链之中的中间件。这个中间件的具体实现利用了区块链中的EventLog日志功能,日志记录了相关的要进行传输上链的数据的信息。

全面一些的解释参考 Chainlink预言机基本原理

二、概览

本预言机实现的功能为从币安的API接口中获取到最新的ETH价格!

要实现预言机的基础功能,需要有两个合约,分别为调用者合约(callerContract.sol),和预言机合约(EthPriceOracle.sol),由调用者合约向预言机合约发起请求,预言机合约接受到请求后,记录一个Event,链下的javascript程序监听预言机合约的Event,如果监听到指定事件就想查询相关的数据,并且在调用预言机合约中的相关函数(setLatestEthPrice),然后setLatestEthPrice函数再调用调用者合约中的callerback函数更新ethPrice的值,这样调用者就收到了所需要的链下数据,并且此数据是被记录在了EventLog中。

现在就开始正式进行编写一个预言机啦!

三、链上合约编写

关于预言机,整个功能的实现主要分为用户合约(callerContract.sol)与预言机合约(EthPriceOracleContract.sol)。利用truffle来方便的为合约进行编译部署测试。

1、创建目录与初始化

1.1 创建项目目录

mkdir oralceDemo && cd oracleDemo 
npm初始化
npm init -y
安装npm相关包
分别npm install以下依赖
[email protected]  // 实测不加版本号安装不下来,找了好久终于找到一个能安装的版本
web3
bn.js 
axios
[email protected]  // 必须要加版本号,此版本对应的是solidity 0.5.0的版本,下载最新版可能无法编译!

1.2 Geth私有链 或 其他测试链

具体教程可以参考我的另一篇博客
唯一有区别的是启动geth的参数由些许不同,在后面会赘述
本项目没有用测试链进行测试,但大体类似

1.3 truffle初始化

创建目录
mkdir caller && mkdir oracle
初始化
cd caller && truffle init
cd oracle && truffle init

此刻 ,目录结构大体如下所示

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

* 来源于 https://cryptozombies.io/zh/lesson/14/chapter/1

非常好!到现在已经完成了本项目中最困难的部分啦!

2、调用者合约(CallerContract.sol )

2.0 调用者合约代码

callerContract.sol

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

    // 2.1 更新oracle地址,并生成实例
    function setOracleInstanceAddress(address _oracleInstanceAddress) public onlyOwner {
    
    
        oracleAddress = _oracleInstanceAddress;
        oracleInstance = EthPriceOracleInterface(oracleAddress);
        emit newOracleAddressEvent(oracleAddress);
    }

    // 更新eth价格--函数返回值是id
    function updateEthPrice() public {
    
    
        uint256 id = oracleInstance.getLatestEthPrice();  
        myRequests[id] = true;
        emit ReceivedNewRequestIdEvent(id);
    }

    // 回调函数,由oracle调用返回数值
    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);
    }

    // 确保只有Oracle合约才能调用回调函数修改价格
    modifier onlyOracle() {
    
    
        require(msg.sender == oracleAddress,"You are not authorized to call this function.");
        _;
    }

}

EthPriceOracleInterface.sol
预言机合约的接口,指明调用函数的名字,参数列表与返回值

pragma solidity ^0.5.0;

contract EthPriceOracleInterface {
  function getLatestEthPrice() public returns (uint256);  //返回的是Id
} 

除了看代码之外,我对写代码中遇到的一些问题进行了一些整理。

2.1 solidity中一个合约调用另外一个合约该如何实现?

  1. 编写要调用的合约的函数的接口,如2.0中的EthPriceOracleInterface.sol
  2. 在原合约中import 该接口
  3. 在合约中声明该接口的实例并用该合约地址初始化
  4. 最后即可在本合约中调用被调用合约的函数

具体过程可以参考上面实例

2.2 函数修饰符如何实现?

由modifier声明
重点关注"_",该符号代表被修饰的函数的代码所处的位置

    modifier onlyOracle() {
    
    
        require(msg.sender == oracleAddress,"You are not authorized to call this function.");
        _;
    }

2.3 什么是openzeppelin库?

什么是openzeppelin库 链接
openzeppelin库如何选择 链接

2.4 什么是mapping?

mapping 是solidity中的映射关系,非常经典的应用是在ERC20中用来保存每一个账户的地址的该代币的余额。

2.5 solidity中合约调用合约的时候,msg.sender是谁?

是从外部发起这个交易的人呢?还是这个调用这个合约的合约呢?
分为不同的情况!
参考文章call delegatecall

3、预言机合约(EthPriceContract.sol)

3.0 预言机合约代码

EthPriceOracle.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);

    // 该函数供caller调用,实现发起一个数据请求!
    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;
    }

    // 调用该函数将ethPrice写入callerContract合约中,调用回调函数
    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);
    }
}

CallerContractInterface.sol

pragma solidity ^0.5.0;

contract CallerContractInterface {
    
    
    function callback(uint256 _ethPrice,uint256 id) public;
}

3.1 如何在solidity中生成一个不那么安全的随机数?

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

这一行代码就可以啦!
为什么不安全?这个内容在本博客开头的链接中有!

四、总结

到此为止,我们的预言机合约与调用者合约的链上部分就完成啦!
接下来,我们将链下对他们所触发的事件进行处理!

猜你喜欢

转载自blog.csdn.net/qq_45469783/article/details/123319313
今日推荐