上篇中介紹了 ChainLink 的 Oracle 是如何達到整理資料與確認資料無誤的機制,但是沒有提到如何在自己的 Smart Contract 中使用他們的程式來達到判斷的效果。因此今天會使用 ChainLink 的 Oracle 合約來達到從外部得到幣價資訊,並拿來改變我們的 NFT metadata。
ChainLink 設計的 Data Feed 系統採用的機制就如昨天提到的最後一個部分,在 DON 的節點會利 CHAIN-LINK-SC 寫好的合約來向外部 Source 取得想要的資訊。
而在 Data Feed 裡面有一個三角形:
AggregatorV3Interface
(ChainLink 會更新他們的合約,目前是 version 3) 來呼叫 proxy 合約中的 function,透過與 proxy 互動的方式來得到想要的資訊。如何簡單的從 DON 中取得資料,可以看以下的例子。
若你想從 Goerli 測試鏈中取得當前的 ETH/USD 匯率,可以這樣寫。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract getPrice {
AggregatorV3Interface internal priceFeed;
constructor() {
priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
}
function getLatestPrice() public view returns (int) {
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = priceFeed.latestRoundData();
return price;
}
}
import 中使用了 AggregatorV3Interface,可以讓繼承他的合約自行定義裡面的 statement。
而在 constructor 中初始化了我們想要使用的合約。先前已經有一個人把 proxy contract 部屬在鏈上了,而0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
這個地址就是一個 proxy contract,因此在我們 import AggregatorV3Interface 後我們就可以直接在我們的合約中呼叫 proxy contract 裡面的函式。
最後在 getLatestPrice()
中呼叫了 priceFeed.lasestRoundData()
這個函式,這時便會呼叫 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
中的 lastestRoundData()
,隨後 Aggregator 便會連接數個 DON 上的節點,並取得資料並提供給 Proxy,最後再由 Proxy 回傳到資料(在 Goerli 上的 ETH/USD 匯率)我們的 getPrice
合約中。
而最後得到的資料長的像這樣:
Aggregator 每一段時間會自動更新數據,而更新的條件有兩個:
Oracle 的概念不只可以用在鏈外與鏈內的檔案傳輸,也可以應用在條件觸發或是自動化管理的合約上。Upkeep 是 ChainLink 設計的一個另一個 Oracle 系統,它的原理跟 Data Feed 類似。在 Keeper 這個系統中有三個角色:
這裡拿一個在 chainlink 官網上的簡單例子來說明:
import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol";
contract Counter is KeeperCompatibleInterface {
uint public counter;
uint public immutable interval; // 我們決定的「一段時間」
uint public lastTimeStamp; // 用來記錄 deploy 或者後來更新的時間戳記
constructor(uint updateInterval) {
interval = updateInterval;
lastTimeStamp = block.timestamp;
counter = 0;
}
function checkUpkeep(bytes calldata /*checkData*/) external view override returns (bool upkeepNeeded, bytes memory /*performData*/) {
upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
// 會確認時間是不是經過了 interval
}
function performUpkeep(bytes calldata /* performData */) external override {
if ((block.timestamp - lastTimeStamp) > interval ) {
lastTimeStamp = block.timestamp;
counter = counter + 1;
}
}
}
首先要繼承 KeeperCompatibleInterface
,如此才能 override
其中的函式。
在 KeeperCompatible
中有兩個特殊的 function,checkUpkeep()
與 performUpkeep()
。
簡單地來說,這兩個 function 其中一個是負責要確認條件是否發生,另一個則是負責來執行你想要做的事情。
checkData
是由 chainLink 在註冊 Keeper 的時候需要設定的值,實際流程與說明詳情請看這裡。傳入後可以在 function 中幫助判斷。performUpkeep()
;第二個則是 bytes,是用來傳入 performUpkeep()
時的資料。checkUpkeep()
return 的 boolean(upkeepNeeded) == true
時,在 Keeper 系統裡的節點會發起一個交易到區塊鏈上,這個交易便是使用 performData
作為 input 來呼叫 performUpkeep()
。如果把上述兩者關係用一張簡圖來表示會長得像:
以上面的例子而言,checkUpkeep()
會在每次新的區塊產出時確認「目前時間戳記」與「上次紀錄的時間戳記」相差是否超過 interval
的值,如果超過了便會讓 counter += 1
。
依照上述的方法,我實作了一個簡單的例子,大致流程像是這樣:
主要有改動的程式大概在這邊:
function safeMint(address to, uint256 tokenId) public {
_safeMint(to, tokenId);
_setTokenURI(tokenId, dog);
}
function checkUpkeep(bytes calldata /*checkData*/) external view override returns (bool upkeepNeeded, bytes memory /*performData*/) {
int currentPrice = getEthPrice();
upkeepNeeded = ( (currentPrice - lastPrice) > 0 );
}
function performUpkeep(bytes calldata /* performData */) external override {
int currentPrice = getEthPrice();
if ( (currentPrice - lastPrice) > 0 ) {
lastPrice = currentPrice;
_setTokenURI(0, cat);
}
}
想看全部程式碼可見這裡
透過 upkeep 系統,可以看到很快的 NFT 的 URI 已經改變了。
今天實作了一個可以根據 ChainLink 上的 Data Feed 得到的幣價來更新 NFT URI 的方法!而在搜尋資訊時,為了了解背後的 Oracle 機制真的花了蠻多的時間,但目前有很多的項目方或是 Defi 背後運行的 Oracle 都是使用 ChainLink,因此的確是值得學習的,希望我可以在 project 中使用其機制來實作出一些自動化的功能。
若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!
歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4