每一个defi项目几乎都需要在合约上获取外部数据。Decentralized Blockchain Oracle就是用来获取外部数据的。
背景(https://witnet.io/)
Witnet是一个Decentralized Blockchain Oracle。最近Witnet在conlux社区做宣传,准备登陆conflux,我也顺便试了一下。
Witnet is a next-gen oracle solution 9 that enables smart contracts to securely access all kind of data with full integrity guarantees (crypto prices, stock prices, weather data, any API, etc.)
Witnet runs on its own single-purpose oracle chain, but it can be side-chained / bridged to multiple smart contract plarforms.
Over the last weeks, several members of the Witnet development community have been collaborating with the Conflux team on making this oracle solution available on Conflux Network.
坑点
在介绍里,下面是一个测试代码例子。看起来非常简单。
pragma solidity >=0.5.0 <0.9.0;
import "ado-contracts/contracts/interfaces/IERC2362.sol";
contract MyContract {
function readFromPriceFeed() external view returns(int256) {
IERC2362 priceFeed = IERC2362("");
bytes32 assetID = bytes32(hex(""));
int256 value = priceFeed.valueFor(assetID);
return value;
}
}
但是当我看了源码,发现IERC2362.sol是这样实现的:
interface IERC2362
{
/**
* @dev Exposed function pertaining to EIP standards
* @param _id bytes32 ID of the query
* @return int,uint,uint returns the value, timestamp, and status code of query
*/
function valueFor(bytes32 _id) external view returns(int256,uint256,uint256);
}
看起来十分惊呆。一开始的代码根本不可能实现功能好吧。很大概率是要自己实现这个接口。
Contract实现
首先要继承几个重要的接口。分别是IERC2362,UsingWitnet,WitnetRequestInitializableBase和Payable。(这里可以直接下载例子:https://github.com/witnet/wit...)
import "ado-contracts/contracts/interfaces/IERC2362.sol";
import "witnet-solidity-bridge/contracts/UsingWitnet.sol";
import "witnet-solidity-bridge/contracts/patterns/Payable.sol";
import "witnet-solidity-bridge/contracts/requests/WitnetRequestInitializableBase.sol";
// Your contract needs to inherit from UsingWitnet
contract ERC2362PriceFeed
is
IERC2362,
Payable,
UsingWitnet,
WitnetRequestInitializableBase
随后,比较关键的是constructor:里面有三个重要参数分别是_wrb,_erc2362str和decimals。_wrb是合约的conflux地址,_erc2362str是一个字符串,用来识别assert的,decimals表示小数点保留多少位。
/// Constructor.
/// @param _wrb WitnetRequestBoard instance, or proxy, address.
/// @param _erc2362str Price feed denomination, according to ERC2362 specs.
/// @param _decimals Fixed number of decimals.
constructor (
WitnetRequestBoard _wrb,
string memory _erc2362str,
uint8 _decimals
)
Payable(address(0))
UsingWitnet(_wrb)
{
creator = msg.sender;
decimals = _decimals;
literal = _erc2362str;
erc2362ID = keccak256(abi.encodePacked(_erc2362str));
}
其次重要的就是valueFor,可以看到返回的价格其实就是最新价格。
/// @notice Exposes the public data point in an ERC2362 compliant way.
/// @dev Returns error `400` if queried for an unknown data point, and `404` if `completeUpdate` has never been called
/// @dev successfully before.
function valueFor(bytes32 _erc2362id)
external view
override
returns (int256, uint256 _timestamp, uint256)
{
// Unsupported data point ID
if(_erc2362id != erc2362ID)
return(0, 0, 400);
_timestamp = lastResponse.timestamp;
return (
int256(uint256(lastPrice)),
_timestamp,
_timestamp == 0 ? 404 : 200
);
}
还有computeUpdate和requestUpdate等等,都是非常重要的函数,具体参考(https://github.com/witnet/wit...)用的时候其实直接复制就好了。
Migration实现
migration同样不太好写,这里有2个概念容易混淆。一个是deployed contract/一个是wrb contract。具体的migrated 代码如下:
/*
* @Author: your name
* @Date: 2021-10-15 14:45:44
* @LastEditTime: 2021-10-18 14:50:58
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
*/
const fs = require("fs")
const { Conflux } = require('js-conflux-sdk');
const conflux = new Conflux({url: 'http://test.confluxrpc.com', networkId: 1});
const ERC2362PriceFeed = artifacts.require("ERC2362PriceFeed")
TokenName = "CfxUsdtPrice";
TokenByteDecimals = 6;
ERC2362ID = "Price-CFX/USDT-6";
// WitnetParserLib = "0x8d683e88E85f785180c68953933bA7752d1b1Dd3";
WitnetRequestBoard = "0x89e0b86eec97bc24f44e3eb206b22b235db58c1e";
// 0x89e0b86eec97bc24f44e3eb206b22b235db58c1e
module.exports = async function (deployer) {
await deployer.deploy(
ERC2362PriceFeed,
WitnetRequestBoard,
ERC2362ID,
TokenByteDecimals
)
let priceFeedContract = await ERC2362PriceFeed.deployed()
console.log(" > Artifact name:\t \"#artifact\"")
console.log(" > Contract name:\t \"" + ERC2362PriceFeed.contractName + "\"")
console.log(" > Contract address:\t \"" + ERC2362PriceFeed.address + "\"")
console.log(" > ERC2362 ID:\t \"" + await priceFeedContract.erc2362ID.call() + "\"")
console.log(" > ERC2362 literal:\t \"" + await priceFeedContract.literal.call() + "\"")
console.log(" > WRB address:\t ", await priceFeedContract.witnet.call())
console.log(" > Bytecode:\t\t ", await priceFeedContract.bytecode.call())
// cfxtest:ace8bsds7wn52khyk29nebzwfpvz5rppd28kcxetj8
const WERAddress = await priceFeedContract.witnet.call();
const byteCode = await conflux.getCode(WERAddress);
const assertID = await priceFeedContract.erc2362ID.call();
const ContractInfo = {
"contractName": "CFXPriceFeedContract",
"contractAddress": WERAddress,
"byteCode": byteCode,
"assertID": assertID
}
// 写入
fs.writeFileSync("./migrations/contractInfo.json", JSON.stringify(ContractInfo), { flag: 'w+'})
};
测试实现
测试部分同样不太好写,有很多细节要注意。我直接贴代码吧。
/*
* @Author: your name
* @Date: 2021-10-17 04:31:22
* @LastEditTime: 2021-10-18 18:11:51
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /pricefeedtest/test/pricefeedTest.js
*/
const fs = require("fs")
const ERC2362PriceFeed = artifacts.require("ERC2362PriceFeed")
const WitnetRequestBoard = artifacts.require("WitnetRequestBoard")
const WitnetRequestBoardTestHelper = artifacts.require("WitnetRequestBoardTestHelper")
const decimals = 6;
const ERC2362ID = "Price-CFX/USDT-6";
let wrbInstance
let ContractInfo
ContractInfo = fs.readFileSync("./migrations/contractInfo.json",{ flag: 'r'})
ContractInfo = JSON.parse(ContractInfo)
console.log(ContractInfo)
const bytecode = ContractInfo.byteCode
contract(ERC2362PriceFeed.contractName, accounts => {
describe("WRB test suite", () => {
let feed, maxReward
let wrbInstance
before(async () => {
const helper = await WitnetRequestBoardTestHelper.new()
wrbInstance = await WitnetRequestBoard.at(helper.address)
})
beforeEach(async () => {
maxReward = web3.utils.toWei("0.1", "ether")
feed = await ERC2362PriceFeed.new(
wrbInstance.address,
ERC2362ID,
decimals
)
await feed.initialize(bytecode)
})
it("check price update", async () => {
await feed.requestUpdate({ value: maxReward })
const id = await feed.requestId()
await wrbInstance.reportResult(id, "0xAA", "0x1b0020000000000000")
await feed.completeUpdate()
const value = await feed.valueFor(await feed.erc2362ID.call())
assert.equal(await feed.pending(), false)
assert.equal(value[0], 0x3f2)
assert.notEqual(value[1], 0)
assert.equal(value[2], 200)
})
})
})
总结
Conflux上第一个抵押算稳项目就快上线了。我在用witnet的时候,遇到很多很多坑点,这里说不清楚。我直接贴上对大家有用的项目代码链接吧。(https://github.com/witnet/wit...)