作者:NEST社羣開發者-YolkLi
NEST 預言機-獲取鏈上價格
介紹
NEST 預言機採用雙邊報價機制生成鏈上價格,質押雙邊資產來保證價格的準確性;完全去中心化的鏈上價格生成機制。
白皮書:https://nestprotocol.org/doc/zhnestwhitepaper.pdf
GitHub:https://github.com/NEST-Protocol
NEST Protocol:https://nestprotocol.org/
嘗試獲取鏈上價格
瞭解機制
NEST 預言機以區塊為單位生成價格,如果區塊內沒有價格,則使用最近的區塊價格。
使用報價的方式生成區塊價格,如果一個區塊內有多筆報價,則加權平均。
每筆報價有 25 區塊的驗證時間(吃單),如果驗證時間內沒有被吃單則代表市場認可這筆報價,將會在報價區塊後的 25 個區塊價格生效。
NEST預言機價格合約 sol 檔案
GitHub:
https://github.com/NEST-Protocol/NEST-oracle-V3/blob/master/NestOffer/Nest_3_OfferPrice.sol
程式碼解析
增加價格
function addPrice(uint256 ethAmount, uint256 tokenAmount, uint256 endBlock, address tokenAddress, address offerOwner) public onlyOfferMain{
// Add effective block price information
TokenInfo storage tokenInfo = _tokenInfo[tokenAddress];
PriceInfo storage priceInfo = tokenInfo.priceInfoList[endBlock];
priceInfo.ethAmount = priceInfo.ethAmount.add(ethAmount);
priceInfo.erc20Amount = priceInfo.erc20Amount.add(tokenAmount);
if (endBlock != tokenInfo.latestOffer) {
// If different block offer
priceInfo.frontBlock = tokenInfo.latestOffer;
tokenInfo.latestOffer = endBlock;
}
}
該方法限制了只有“報價合約”才可以呼叫,保證新增到價格合約中的價格資料的資料來源正確。
輸入引數 | 描述 |
---|---|
ethAmount | 報價 ETH 數量 |
tokenAmount | 報價 ERC20 Token 數量 |
endBlock | 價格生效區塊號 |
tokenAddress | 報價的 ERC20 Token 合約地址 |
offerOwner | 報價者錢包地址 |
PriceInfo storage priceInfo = tokenInfo.priceInfoList[endBlock];
priceInfo.ethAmount = priceInfo.ethAmount.add(ethAmount);
priceInfo.erc20Amount = priceInfo.erc20Amount.add(tokenAmount);
這三行程式碼實現在同一個區塊內加權平均。
修改價格
function changePrice(uint256 ethAmount, uint256 tokenAmount, address tokenAddress, uint256 endBlock) public onlyOfferMain {
TokenInfo storage tokenInfo = _tokenInfo[tokenAddress];
PriceInfo storage priceInfo = tokenInfo.priceInfoList[endBlock];
priceInfo.ethAmount = priceInfo.ethAmount.sub(ethAmount);
priceInfo.erc20Amount = priceInfo.erc20Amount.sub(tokenAmount);
}
同樣限制了只有“報價合約”才有許可權呼叫。只有在觸發吃單操作後,才會修改對應生效區塊中的價格,將”新增價格“時的報價數量按照”吃單“規模減掉。
輸入引數 | 描述 |
---|---|
ethAmount | 吃單 ETH 數量 |
tokenAmount | 吃單 ERC20 數量 |
tokenAddress | 報價 ERC20 地址 |
endBlock | 價格生效區塊號 |
獲取價格(最新)
function updateAndCheckPriceNow(address tokenAddress) public payable returns(uint256 ethAmount, uint256 erc20Amount, uint256 blockNum) {
require(checkUseNestPrice(address(msg.sender)));
mapping(uint256 => PriceInfo) storage priceInfoList = _tokenInfo[tokenAddress].priceInfoList;
uint256 checkBlock = _tokenInfo[tokenAddress].latestOffer;
while(checkBlock > 0 && (checkBlock >= block.number || priceInfoList[checkBlock].ethAmount == 0)) {
checkBlock = priceInfoList[checkBlock].frontBlock;
}
require(checkBlock != 0);
PriceInfo memory priceInfo = priceInfoList[checkBlock];
address nToken = _tokenMapping.checkTokenMapping(tokenAddress);
if (nToken == address(0x0)) {
_abonus.switchToEth.value(_priceCost)(address(_nestToken));
} else {
_abonus.switchToEth.value(_priceCost)(address(nToken));
}
if (msg.value > _priceCost) {
repayEth(address(msg.sender), msg.value.sub(_priceCost));
}
emit NowTokenPrice(tokenAddress,priceInfo.ethAmount, priceInfo.erc20Amount);
return (priceInfo.ethAmount,priceInfo.erc20Amount, checkBlock);
}
輸入引數 | 描述 |
---|---|
tokenAddress | ERC20 Token 合約地址 |
輸出引數 | 描述 |
---|---|
ethAmount | ETH 數量 |
erc20Amount | ERC20 Token 數量 |
blockNum | 生效價格區塊 |
require(checkUseNestPrice(address(msg.sender)));
檢查是否有許可權使用 NEST 價格。
mapping(uint256 => PriceInfo) storage priceInfoList = _tokenInfo[tokenAddress].priceInfoList;
獲取對應 Token 的價格資料來源。
uint256 checkBlock = _tokenInfo[tokenAddress].latestOffer;
while(checkBlock > 0 && (checkBlock >= block.number || priceInfoList[checkBlock].ethAmount == 0)) {
checkBlock = priceInfoList[checkBlock].frontBlock;
}
解釋一下 while 迴圈的判斷,需要從最新的報價區塊開始往後倒推找到當前已經生效並且沒有被吃單的價格資料所在的區塊號(checkBlock)。
require(checkBlock != 0);
這個判斷個人猜測是為了防止有些 token 剛開始報價,還沒有有效價格生成,又因為呼叫價格是要付費的。所以加了限制,如果沒找到生效價格的區塊號,交易直接失敗。
PriceInfo memory priceInfo = priceInfoList[checkBlock];
address nToken = _tokenMapping.checkTokenMapping(tokenAddress);
if (nToken == address(0x0)) {
_abonus.switchToEth.value(_priceCost)(address(_nestToken));
} else {
_abonus.switchToEth.value(_priceCost)(address(nToken));
}
if (msg.value > _priceCost) {
repayEth(address(msg.sender), msg.value.sub(_priceCost));
}
這部分程式碼是將呼叫者支付的預言機費用,分配到對應的收益池中。多餘的費用退還給呼叫者。
鏈下獲取價格(最新價格)
// Check real-time price - user account only
function checkPriceNow(address tokenAddress) public view returns (uint256 ethAmount, uint256 erc20Amount, uint256 blockNum) {
require(address(msg.sender) == address(tx.origin), "It can't be a contract");
mapping(uint256 => PriceInfo) storage priceInfoList = _tokenInfo[tokenAddress].priceInfoList;
uint256 checkBlock = _tokenInfo[tokenAddress].latestOffer;
while(checkBlock > 0 && (checkBlock >= block.number || priceInfoList[checkBlock].ethAmount == 0)) {
checkBlock = priceInfoList[checkBlock].frontBlock;
}
if (checkBlock == 0) {
return (0,0,0);
}
PriceInfo storage priceInfo = priceInfoList[checkBlock];
return (priceInfo.ethAmount,priceInfo.erc20Amount, checkBlock);
}
原理和上一個方法一樣。區別是禁止了合約呼叫和不需要付費。應該是為了給鏈下應用檢視價格使用。
啟用呼叫許可權
function activation() public {
_nestToken.safeTransferFrom(address(msg.sender), _destructionAddress, destructionAmount);
_addressEffect[address(msg.sender)] = now.add(effectTime);
}
使用 NEST 預言機需要質押一定數量的 NEST 和等待一天。這個操作應該是為了防止”合約盜取價格“。如果沒有這個限制可以寫個代理合約,獲取價格,只需要支付一次費用,其他的呼叫者可以一起使用價格。
DEMO
官方文件
/**
* @dev Get a single price
* @param token Token address of the price
*/
function getSinglePrice(address token) public payable {
// In consideration of future upgrades, the possibility of upgrading the price contract is not ruled out, and the voting contract must be used to query the price contract address.
Nest_3_OfferPrice _offerPrice = Nest_3_OfferPrice(address(_voteFactory.checkAddress("nest.v3.offerPrice")));
// Request the latest price, return the eth quantity, token quantity, and effective price block number. Tentative fee.
(uint256 ethAmount, uint256 tokenAmount, uint256 blockNum) = _offerPrice.updateAndCheckPriceNow.value(0.001 ether)(token);
uint256 ethMultiple = ethAmount.div(1 ether);
uint256 tokenForEth = tokenAmount.div(ethMultiple);
// If the eth paid for the price is left, it needs to be processed.
// ........
emit price(ethAmount, tokenAmount, blockNum, ethMultiple, tokenForEth);
}
/**
* @dev Get multiple prices
* @param token The token address of the price
* @param priceNum Get the number of prices, sorted from the latest price
*/
function getBatchPrice(address token, uint256 priceNum) public payable {
// In consideration of future upgrades, the possibility of upgrading the price contract is not ruled out, and the voting contract must be used to query the price contract address.
Nest_3_OfferPrice _offerPrice = Nest_3_OfferPrice(address(_voteFactory.checkAddress("nest.v3.offerPrice")));
/**
* The returned array is an integer multiple of 3, 3 data is a price data.
* Corresponding respectively, eth quantity, token quantity, effective price block number.
*/
uint256[] memory priceData = _offerPrice.updateAndCheckPriceList.value(0.01 ether)(token, priceNum);
// Data processing
uint256 allTokenForEth = 0;
uint256 priceDataNum = priceData.length.div(3);
for (uint256 i = 0; i < priceData.length;) {
uint256 ethMultiple = priceData[i].div(1 ether);
uint256 tokenForEth = priceData[i.add(1)].div(ethMultiple);
allTokenForEth = allTokenForEth.add(tokenForEth);
i = i.add(3);
}
// Average price
uint256 calculationPrice = allTokenForEth.div(priceDataNum);
// If the eth paid for the price is left, it needs to be processed.
// ........
emit averagePrice(calculationPrice);
}
CoFiX
GitHub:
https://github.com/Computable-Finance/CoFiX/blob/master/contracts/CoFiXController.sol#L282
function getLatestPrice(address token) internal returns (uint256 _ethAmount, uint256 _erc20Amount, uint256 _blockNum) {
uint256 _balanceBefore = address(this).balance;
address oracle = voteFactory.checkAddress("nest.v3.offerPrice");
uint256[] memory _rawPriceList = INest_3_OfferPrice(oracle).updateAndCheckPriceList{value: msg.value}(token, 1);
require(_rawPriceList.length == 3, "CoFiXCtrl: bad price len");
// validate T
uint256 _T = block.number.sub(_rawPriceList[2]).mul(timespan);
require(_T < 900, "CoFiXCtrl: oralce price outdated");
uint256 oracleFeeChange = msg.value.sub(_balanceBefore.sub(address(this).balance));
if (oracleFeeChange > 0) TransferHelper.safeTransferETH(msg.sender, oracleFeeChange);
return (_rawPriceList[0], _rawPriceList[1], _rawPriceList[2]);
// return (K_EXPECTED_VALUE, _rawPriceList[0], _rawPriceList[1], _rawPriceList[2], KInfoMap[token][2]);
}
NEST 開發者交流:https://t.me/nestdevs(收益領取、預言機價格呼叫、前端接入等開發交流)