在我们做去中心化应用开发时,其中有两个头疼的问题,一是想要获取去中心化的一些数据,如代币价格等,还有一个就是安全的创建随机数,这篇文章带大家了解一下这两块的实际应用。
一、调用 Chainklink 获取外部数据
1. Chainlink 预言机介绍
由于智能合约无法调用外部 API
这一特性,所以诞生了预言机这个机制用来帮助智能合约获取外部数据,除了应用最广泛的价格数据以外,还包括一些天气数据,体育比赛数据,股票市场数据,交通数据,甚至包括总统选结果等数据。
除了提供数据,预言机广义上的功能也包括提供随机数和作为触发器实现智能合约执行,它们都算是链下的工具来和链上的合约进行交互。
而 Chainlink
是目前预言机数据的最大提供平台。
2. Chainlink 获取数据示例
ts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED
* VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/**
* If you are reading data feeds on L2 networks, you must
* check the latest answer from the L2 Sequencer Uptime
* Feed to ensure that the data is accurate in the event
* of an L2 sequencer outage. See the
* https://docs.chain.link/data-feeds/l2-sequencer-feeds
* page for details.
*/
contract DataConsumerV3 {
AggregatorV3Interface internal dataFeed;
/**
* Network: Sepolia
* Aggregator: BTC/USD
* Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
*/
constructor() {
dataFeed = AggregatorV3Interface(
0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
);
}
/**
* Returns the latest answer.
*/
function getPrice() public view returns (int) {
// prettier-ignore
(
/* uint80 roundID */,
int answer,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = dataFeed.latestRoundData();
return answer / (10 ** 8);
}
}
上面是一个获取BTC/USD
价格的智能合约示例。
- 首先
import
名为AggregatorV3Interface
的接口,这个接口由chainlink
官方的包提供。 constructor
构造函数中,初始化一个名为dataFeed
的AggregatorV3Interface
接口对象,在初始化的接口函数中传入Chainklink
部署的对应BTC/USD
价格的合约地址,这里因为是用的Sepolia
测试网,具体每个网络可以获取哪些价格数据的合约地址,点此查看- 最后通过一个
getPrice
函数调用dataFeed
的latestRoundData
方法来获取价格,返回的answer
参数就是具体的价格,但每个交易对的金额精度不一样,一般要除以金额精度才是真正的USD
单位,如这里的精度是8
,最终answer
需要除以10 ** 8
才是USD
3. 通过 Remix 部署合约测试
我们的智能合约需要调用Sepolia
其他智能合约,所以没办法直接在本地测试,需要部署到Sepolia
测试网上,建议通过Remix
来部署
首先在MetaMask
钱包中添加Sepolia
网络,然后在Remix
部署界面选择使用MetaMask
钱包部署,最后点击Deploy
按钮部署。
部署成功后就可以看到我们已经部署的智能合约,执行getPrice
方法来获取价格。
更详细的获取外部数据文档,点此查看官方文档
二、调用 Chainlink 生成随机数
1. Solidity 直接生成随机数
ts
/**
* 链上伪随机数生成
* 利用keccak256()打包一些链上的全局变量/自定义变量
* 返回时转换成uint256类型
*/
function getRandomOnchain() public view returns(uint256){
// remix运行blockhash会报错
bytes32 randomBytes = keccak256(abi.encodePacked(block.number, msg.sender, blockhash(block.timestamp-1)));
return uint256(randomBytes);
}
主要是利用keccak256
函数根据区块高度
、当前调用人
、时间戳
等生成随机数,但是这种方法并不安全,因为结果对于矿工是可预测的,如我们做一款Web3彩票项目
,矿工可以选择根据这个生成结果是否中奖选择是否上报这个区块。
2. 调用 Chainklink VRF 生成随机数示例
ts
// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFV2WrapperConsumerBase.sol";
/**
* Request testnet LINK and ETH here: https://faucets.chain.link/
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
*/
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract VRFv2DirectFundingConsumer is
VRFV2WrapperConsumerBase,
ConfirmedOwner
{
event RequestSent(uint256 requestId, uint32 numWords);
event RequestFulfilled(
uint256 requestId,
uint256[] randomWords,
uint256 payment
);
struct RequestStatus {
uint256 paid; // amount paid in link
bool fulfilled; // whether the request has been successfully fulfilled
uint256[] randomWords;
}
mapping(uint256 => RequestStatus)
public s_requests; /* requestId --> requestStatus */
// past requests Id.
uint256[] public requestIds;
uint256 public lastRequestId;
// Depends on the number of requested values that you want sent to the
// fulfillRandomWords() function. Test and adjust
// this limit based on the network that you select, the size of the request,
// and the processing of the callback request in the fulfillRandomWords()
// function.
uint32 callbackGasLimit = 100000;
// The default is 3, but you can set this higher.
uint16 requestConfirmations = 3;
// For this example, retrieve 2 random values in one request.
// Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.
uint32 numWords = 2;
// Address LINK - hardcoded for Sepolia
address linkAddress = 0x779877A7B0D9E8603169DdbD7836e478b4624789;
// address WRAPPER - hardcoded for Sepolia
address wrapperAddress = 0xab18414CD93297B0d12ac29E63Ca20f515b3DB46;
constructor()
ConfirmedOwner(msg.sender)
VRFV2WrapperConsumerBase(linkAddress, wrapperAddress)
{}
function requestRandomWords()
external
onlyOwner
returns (uint256 requestId)
{
requestId = requestRandomness(
callbackGasLimit,
requestConfirmations,
numWords
);
s_requests[requestId] = RequestStatus({
paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit),
randomWords: new uint256[](0),
fulfilled: false
});
requestIds.push(requestId);
lastRequestId = requestId;
emit RequestSent(requestId, numWords);
return requestId;
}
function fulfillRandomWords(
uint256 _requestId,
uint256[] memory _randomWords
) internal override {
require(s_requests[_requestId].paid > 0, "request not found");
s_requests[_requestId].fulfilled = true;
s_requests[_requestId].randomWords = _randomWords;
emit RequestFulfilled(
_requestId,
_randomWords,
s_requests[_requestId].paid
);
}
function getRequestStatus(
uint256 _requestId
)
external
view
returns (uint256 paid, bool fulfilled, uint256[] memory randomWords)
{
require(s_requests[_requestId].paid > 0, "request not found");
RequestStatus memory request = s_requests[_requestId];
return (request.paid, request.fulfilled, request.randomWords);
}
/**
* Allow withdraw of Link tokens from the contract
*/
function withdrawLink() public onlyOwner {
LinkTokenInterface link = LinkTokenInterface(linkAddress);
require(
link.transfer(msg.sender, link.balanceOf(address(this))),
"Unable to transfer"
);
}
}
这是一个Sepolia
测试网的示例,每个网络下linkAddress
和wrapperAddress
地址不同, 每个网络具体可以 点此查看
3. 部署及测试
部署的时候同样选择MetaMask
连接到Sepolia
网络,部署好了后,我们还需要给部署好的合约转入LINK
代币(很重要的)。
复制这个合约地址,用MetaMask
钱包给这个地址转入LINK
代币2-3个左右(测试用)。
转入成功后就可以调用requestRandomWords
方法,它会生成一个requestId
,这个requestId
可以通过调用lastRequestId
方法查看。
最后通过给getRequestStatus
方法传入requestId
参数并调用就可以查看Chainlink
响应给我们的随机字符串
了。
具体的获取随机数官方文档,点此查看
这样我们就最终实现了在Solidity 智能合约中调用 Chainklink 获取外部数据及生成随机数,如果各位小伙伴有什么问题,也欢迎留言哦!