今天將會透過 lession 19- Data Feeds and Computation 來介紹如何引用 Oracle
Ethereum Oracle 是讓 Smart Contract 讀取外部資料的一種方式,因為預設 Smart Contract 寫入的邏輯都是透過 Blockchain 交易與 EVM 來運行所以都是具有可預測性,所以需要這樣的機制來增加應用彈性。
當 Smart Contract 有使用 Oracle 來引入外部資料,這類 Smart Contract 稱作 Hybrid Smart Contract
因為 Smart Contract 本身無法直接存取外部資料
所以必須透過 Oracle 這個機制引入外部資料提供者
然而如果使用中心化的 Oracle 會導致資料會容易被單點錯誤所影響
所以理想上的作法是使用去中心化 Oracle 網路(Decentrallized Oracle Network) 以及去中心化資料來。
ChainLink 是一個去中心化 Oracle 網路(Decentrallized Oracle Network)的框架,可以透過多個資料來源拿取資料。
去中心化 Oracle 網路(Decentrallized Oracle Network)以去中心化方式匯集資料把資料放到區塊鏈上的一個 Smart Contract(稱作 price reference feed 或是 data feed) 讓其他人使用。
所以要使用 ChainLink ,就是讀取使用 ChainLink 網路資料的 SmartContract。
pragma solidity ^0.6.7; //1. Enter Solidity version here
//2. Create the `PriceConsumerV3`contract
contract PriceConsumerV3 {
}
當要使用其他寫好的 code
solidity 可以直接使用 import 從可以讀取到的資源引入
這邊需要使用 ChainLink 的 AggregatorV3Interface
以下是 interface 的內容
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface AggregatorV3Interface {
function decimals()
external
view
returns (
uint8
);
function description()
external
view
returns (
string memory
);
function version()
external
view
returns (
uint256
);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(
uint80 _roundId
)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
pragma solidity ^0.6.7;
// Start here
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
}
上面引入 interface
接下就可以透過內部的 Contract 來讀取想要的資料
pragma solidity ^0.6.7;
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
// 1. Create a `public` variable named `priceFeed` of type `AggregatorV3Interface`.
AggregatorV3Interface public priceFeed;
// 2. Create a constructor
constructor() public {
priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
}
// 3. Instantiate the `AggregatorV3Interface` contract
}
現在需要使用 lastestRoundData 來取得最新的資料
其中會有以下資訊:
在呼叫 function 之前,需要知道資料 tuples 結構
在 solidity, tuple 是一種把多個變數組合成一個結構的表達式
如下
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
這個 latestRoundData 的回傳值就是一個 tuple
接收 tuple 回傳值語法如下
(uint80 roundId, int answer, uint startedAt, uint updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
另外也可以把回傳值變數重新命名如下
(uint80 roundId, int price, uint startedAt, uint updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
此外,也可以只接收部份回傳值如下
(,int price,,,) = priceFeed.latestRoundData();
pragma solidity ^0.6.7;
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
AggregatorV3Interface public priceFeed;
constructor() public {
priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
}
// Start here
function getLatestPrice() public view returns(int) {
(,int price,,,) = priceFeed.latestRoundData();
return price;
}
}
如果直接讀取 getLatestPrice 得到的輸出格式會什麼?
會發現他的回傳值格式如下
310523971888
但實際的價格卻是 $3,105.52.
所以必須要呼叫一個叫作 decimal 的 function
來取出換成 decimal 的值
pragma solidity ^0.6.7;
import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
AggregatorV3Interface public priceFeed;
constructor() public {
priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
}
function getLatestPrice() public view returns (int) {
(,int price,,,) = priceFeed.latestRoundData();
return price;
}
// Start here
function getDecimals() public view returns(uint8) {
uint8 decimals = priceFeed.decimals();
return decimals;
}
}
在前面 ZombieContract 有使用一個隨機數的功能
然而這個隨機數功能並非是完全無法預測也就是並非真的隨機
前面實作的方式是透過 kecca256 產生的 hash
並且使用時間參數 block.timestamp 與一個亂數種子 block.difficulty 以及簽章者的 address msg.sender
具體實作如下
uint(keccak256(abi.encodePacked(msg.sender, block.difficulty, block.timestamp)));
所以當這3個資訊一出其實這個隨機數功能是一個可以預測的數列
因此要做到真正的隨機數必須透過 Chainlink VRF(Verifiable Randomness Function) 來處理
Chainlink VRF(Verifiable Randomness Function) 是一種從鏈下取隨機數的一種方法,使用的是被證明過的密碼學方法。
這部份是為了達到真正的隨機性。
其他想從鏈下取得隨機性的方法是透過呼叫鏈下的 api 來做到,但如果鏈下 api 對應的 service 掛了或是被竄改,就可能拿到非隨機性的結果。
VRF 使用鏈上的驗證 Contract 來使用密碼學上方式證明有達到真正的隨機性。
流程如下:
流程分成以下步驟
可以發現為了要使用 Oracle 除了本身運算之外,還需要多付一個關於 Oracle 驗證的 Transaction fee 。而這個 Transaction fee 是透過 Chainlink token 支付的。
看上面的流程與 data feed 與類似
那位何不直接使用 data feed 要透過 VRF。
主要是節省成本,因為是用 data feed 代表所有 Chianlink 節點都需要做這個運算,而不是像 VRF 只需要單個節點。
pragma solidity ^0.6.6;
// 1. Import the ""@chainlink/contracts/src/v0.6/VRFConsumerBase.sol" contract
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
contract ZombieFactory {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
function _createZombie(string memory _name, uint _dna) private {
zombies.push(Zombie(_name, _dna));
}
function _generatePseudoRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
}
為了要與 Chainlink Node 互動
需要知道一些變數
import "./Y.sol";
contract X is Y {
constructor() Y() public{
}
}
上面就是使用繼承來實作 constructor
而要繼承 VRFConsumerbase 的 constructor 語法如下:
constructor() VRFConsumerBase(
0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
) public{
}
pragma solidity ^0.6.6;
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
// 1. Have our `ZombieFactory` contract inherit from the `VRFConsumerbase` contract
contract ZombieFactory is VRFConsumerbase {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
// 2. Create a constructor
constructor() VRFConsumerBase(
0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
) public{
}
function _createZombie(string memory _name, uint _dna) private {
zombies.push(Zombie(_name, _dna));
}
function _generatePseudoRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
}
pragma solidity ^0.6.6;
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
contract ZombieFactory is VRFConsumerbase {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
// 1. Define the `keyHash`, `fee`, and `randomResult` variables. Don't forget to make them `public`.
bytes32 public keyHash;
uint256 public fee;
uint256 public randomResult;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
constructor() VRFConsumerBase(
0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
) public{
// 2. Fill in the body
keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
fee = 100000000000000000;
}
function _createZombie(string memory _name, uint _dna) private {
zombies.push(Zombie(_name, _dna));
}
function _generatePseudoRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
}
在 Chainlink VRF 流程
需要定義以下功能:
因為已經引入了 VRFConsumerbase Contract
所以可以使用以下兩個內建的 function
備註 這邊特別限定 fulfillRandomness 為 internal override 只讓 VRF Coordinator Contract 做呼叫
pragma solidity ^0.6.6;
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
contract ZombieFactory is VRFConsumerbase {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
bytes32 public keyHash;
uint256 public fee;
uint256 public randomResult;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
constructor() VRFConsumerBase(
0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
) public{
keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
fee = 100000000000000000;
}
function _createZombie(string memory _name, uint _dna) private {
zombies.push(Zombie(_name, _dna));
}
// 1. Create the `getRandomNumber` function
function getRandomNumber() public returns (bytes32 requestId) {
return requestRandomness(keyHash, fee);
}
// 2. Create the `fulfillRandomness` function
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomResult = randomness;
}
function _generatePseudoRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
}
pragma solidity ^0.6.6;
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
contract ZombieFactory is VRFConsumerbase {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
bytes32 public keyHash;
uint256 public fee;
uint256 public randomResult;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
constructor() VRFConsumerBase(
0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator
0x01BE23585060835E02B77ef475b0Cc51aA1e0709 // LINK Token
) public{
keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
fee = 100000000000000000;
}
function _createZombie(string memory _name, uint _dna) private {
zombies.push(Zombie(_name, _dna));
}
function getRandomNumber() public returns (bytes32 requestId) {
return requestRandomness(keyHash, fee);
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomResult = randomness;
}
}
到此,就透過 Chainlink VRF 完成 random number功能。