NEST预言机-获取链上价格

NEST预言机采用双边报价机制生成链上价格,质押双边资产来保证价格的准确性。是完全去中心化的链上价格生成机制。

# NEST预言机-获取链上价格 ## 介绍 NEST预言机采用双边报价机制生成链上价格,质押双边资产来保证价格的准确性。是完全去中心化的链上价格生成机制。 白皮书:[https://nestprotocol.org/doc/zhnestwhitepaper.pdf](https://nestprotocol.org/doc/zhnestwhitepaper.pdf) github:[https://github.com/NEST-Protocol](https://github.com/NEST-Protocol) > 详细的介绍可以查看[https://nestprotocol.org/](https://nestprotocol.org/) ## 尝试获取链上价格 ### 了解机制 1. NEST预言机以区块为单位生成价格,如果区块内没有价格,则使用最近的区块价格。 2. 使用报价的方式生成区块价格,如果一个区块内有多笔报价,则加权平均。 3. 每笔报价有25区块的验证时间(吃单),如果验证时间内没有被吃单则代表市场认可这笔报价,将会在报价区块后的25个区块价格生效。 “获取价格合约”github:[https://github.com/NEST-Protocol/NEST-oracle-V3/blob/master/NestOffer/Nest_3_OfferPrice.sol](https://github.com/NEST-Protocol/NEST-oracle-V3/blob/master/NestOffer/Nest_3_OfferPrice.sol) ### 代码解析 #### 增加价格 ```c 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数量 | | endBlock | 价格生效区块号 | | tokenAddress | 报价ERC20地址 | | offerOwner | 报价者地址 | ```c PriceInfo storage priceInfo = tokenInfo.priceInfoList[endBlock]; priceInfo.ethAmount = priceInfo.ethAmount.add(ethAmount); priceInfo.erc20Amount = priceInfo.erc20Amount.add(tokenAmount); ``` 这三行代码实现在同一个区块内加权平均。 #### 修改价格 ```c 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 | 价格生效区块号 | #### 获取价格(最新) ```c 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地址 | |输出参数| 描述 | |--|--| | ethAmount | ETH数量 | | erc20Amount | ERC20数量 | | blockNum | 生效价格区块 | ```c require(checkUseNestPrice(address(msg.sender))); ``` 检查是否有权限使用NEST价格。 ```c mapping(uint256 => PriceInfo) storage priceInfoList = _tokenInfo[tokenAddress].priceInfoList; ``` 获取对应Token的价格数据源。 ```c uint256 checkBlock = _tokenInfo[tokenAddress].latestOffer; while(checkBlock > 0 && (checkBlock >= block.number || priceInfoList[checkBlock].ethAmount == 0)) { checkBlock = priceInfoList[checkBlock].frontBlock; } ``` 解释一下while循环的判断,需要从最新的报价区块开始往后倒推找到当前已经生效并且没有被吃单的价格数据所在的区块号(checkBlock)。 ```c require(checkBlock != 0); ``` 这个判断个人猜测是为了防止有些token刚开始报价,还没有有效价格生成,又因为调用价格是要付费的。所以加了限制,如果没找到生效价格的区块号,交易直接失败。 ```c 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)); } ``` 这部分代码是将调用者支付的预言机费用,分配到对应的收益池中。多余的费用退还给调用者。 #### 链下获取价格(最新价格) ```c // 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); } ``` 原理和上一个方法一样。区别是禁止了合约调用和不需要付费。应该是为了给链下应用查看价格使用。 #### 激活调用权限 ```c function activation() public { _nestToken.safeTransferFrom(address(msg.sender), _destructionAddress, destructionAmount); _addressEffect[address(msg.sender)] = now.add(effectTime); } ``` 使用NEST预言机需要质押一定数量的NEST和等待一天。这个操作应该是为了防止”合约盗取价格“。如果没有这个限制可以写个代理合约,获取价格,只需要支付一次费用,其他的调用者可以一起使用价格。 ### DEMO #### 官方文档 ```c /** * @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](https://github.com/Computable-Finance/CoFiX/blob/master/contracts/CoFiXController.sol#L282) ```c 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预言机-获取链上价格

介绍

NEST预言机采用双边报价机制生成链上价格,质押双边资产来保证价格的准确性。是完全去中心化的链上价格生成机制。

白皮书:https://nestprotocol.org/doc/zhnestwhitepaper.pdf github:https://github.com/NEST-Protocol

详细的介绍可以查看https://nestprotocol.org/

尝试获取链上价格

了解机制

  1. NEST预言机以区块为单位生成价格,如果区块内没有价格,则使用最近的区块价格。
  2. 使用报价的方式生成区块价格,如果一个区块内有多笔报价,则加权平均。
  3. 每笔报价有25区块的验证时间(吃单),如果验证时间内没有被吃单则代表市场认可这笔报价,将会在报价区块后的25个区块价格生效。

“获取价格合约”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数量
endBlock 价格生效区块号
tokenAddress 报价ERC20地址
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地址
输出参数 描述
ethAmount ETH数量
erc20Amount ERC20数量
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 &lt; 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 &lt; 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]);
    }

区块链技术网。

  • 发表于 2020-11-09 17:27
  • 阅读 ( 809 )
  • 学分 ( 23 )
  • 分类:Solidity

评论