Skip to content

Commit 0b37d09

Browse files
committed
fix readme && test code for task 14
1 parent f270ff5 commit 0b37d09

File tree

8 files changed

+213
-158
lines changed

8 files changed

+213
-158
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
PRIVATE_KEY=xxxxxxxxxxxxxxxx
22
INFURA_ID=yyyyyyyy
3-
RandomNumberConsumer_ADDRESS=cccccccc
3+
RandomNumberConsumer_ADDRESS=cccccccc
4+
SubscriptionId=ddddd

basic/14-chainlink-price-feed/README.md

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 喂价预言机
1+
# 喂价 和 随机数 预言机
22

33
区块链是非常安全可靠的价值交换网络,但却无法安全防篡改地获取链下数据或将数据发送至链下系统。使用 Chainlink 预言机喂价, 通过预言机网络在链上直接获取实时金融市场价格数据
44

@@ -73,29 +73,66 @@ Chainlink VRF 可验证随机函数, 是一种可证明公平且可验证的
7373

7474
### 操作流程
7575

76-
1. 在 kovan 测试网络环境下,将 Link token (测试代币)添加到小狐狸钱包,初始会自动发放 10 个测试代币,token address:
77-
`0xa36085F69e2889c224210F603D836748e7dC0088`
78-
2. 运行部署脚本部署合约
76+
1. 创建 ChainLink SubscriptionID
77+
登陆 [ChainLink VRF 测试网](https://vrf.chain.link/?_ga=2.225785050.1950508783.1645630272-1230768383.1643005305) , 点击 "Create Subscription" 创建 SubscriptionID , 之后可以在 "My Subscriptions" 中看到创建的 SubscriptionID
78+
<center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/14-chainlink-price-feed/ChainLinkVRF.png?raw=true" /></center>
79+
80+
81+
2. 保存 SubscriptionID
82+
将上一步创建的 SubscriptionID 保存到 .env 文件中
83+
<center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/14-chainlink-price-feed/SubscriptionID.png?raw=true" /></center>
84+
85+
```sh
86+
## .env
87+
SubscriptionId=ddddd
88+
```
89+
90+
3. 运行部署脚本部署合约
7991

8092
```sh
81-
npx hardhat run scripts/02-RandomNumberConsumerDeploy.js --network kovan
93+
npx hardhat run scripts/02-RandomNumberConsumerDeploy.js --network rinkeby
8294
```
8395

84-
3. 使用小狐狸向合约转账 Link token 作为调用随机函数的费用。在 kovan 网络下,合约每次调用随机函数花费 0.1Link,转账适量即可。
8596
4. 将打印出来的合约部署地址,添加到 .env 文件中,运行测试脚本
8697

8798
```js
8899
// .env
89100
RandomNumberConsumer_ADDRESS=xxxx; // <--- you need fill this
90101
```
91102

92-
运行测试脚本
103+
5. 获取 ChainLink 币
104+
登陆 [ChainLink Faucet](https://faucets.chain.link/) , 在, 获取 ChainLink 币用于后续的 RandomNumberConsume , 其中 Network 选择 rinkeby, "Testnet account address" 输入合约 owner 的账户地址
105+
<center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/14-chainlink-price-feed/ChainLinkFaucet.png?raw=true" /></center>
106+
107+
108+
6. 赋权合约消费 ChainLink 币以进行随机数获取
109+
登陆 [ChainLink VRF 测试网](https://vrf.chain.link/?_ga=2.225785050.1950508783.1645630272-1230768383.1643005305) , 点击其中的 SubscriptionID
110+
<center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/14-chainlink-price-feed/ClickSubscriptionID.png?raw=true" /></center>
111+
112+
113+
之后在新出现的页面中, 进行 "Add Funds" 和 "Add consumer". 其中 "Add Funds" 为存入 ChainLink 币的数量, "Add consumer" 需要填入部署成功的 RandomNumberConsumer 合约地址, 即为 .env 文件中 RandomNumberConsumer_ADDRESS 的值
114+
<center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/14-chainlink-price-feed/AddFundsAddCustomer.png?raw=true" /></center>
115+
116+
117+
6. 运行测试脚本
93118

94119
```sh
95-
npx hardhat test ./test/RandomNumberConsumer.test.js --network kovan
120+
npx hardhat run scripts/03-RandomNumberConsumer --network rinkeby
96121
```
97122

98-
结果可能需要等待 2 到 3 分钟,可以看到两次获取的随机数值不同
123+
结果可能需要等待 2 到 3 分钟,可以看到 ChainLink 返回的两个随机值
124+
125+
```sh
126+
❯ npx hardhat run scripts/03-RandomNumberConsumer.js --network rinkeby
127+
Listen on random number call...
128+
Listen on random number result...
129+
first transaction hash: 0xb822b742836e3e028102b938ff9b52f5c31ecbf00a663b4865c50f83d141c441
130+
event RequestId(address,uint256)
131+
random0 requestID: BigNumber { value: "68813323376039607636454911576409413136200025762802867082556497319163019860937" }
132+
event FulfillRandomness(uint256,uint256[])
133+
args[0] : BigNumber { value: "68813323376039607636454911576409413136200025762802867082556497319163019860937" }
134+
random0Res: 21345191237588857524675400331731955708910062406377169110385405370996391926856,49611358654743768743671276783545638722996121599596073254340228099561828202433
135+
```
99136
100137
## todo
101138
Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,77 @@
11
// SPDX-License-Identifier: MIT
2+
// An example of a consumer contract that relies on a subscription for funding.
23
pragma solidity ^0.8.7;
34

4-
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
5+
import '@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol';
6+
import '@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol';
7+
import '@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol';
58

6-
/**
7-
* THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
8-
* PLEASE DO NOT USE THIS CODE IN PRODUCTION.
9-
*/
9+
contract RandomNumberConsumer is VRFConsumerBaseV2 {
10+
event FulfillRandomness(uint256,uint256[]);
11+
event RequestId(address,uint256);
1012

11-
/**
12-
* Request testnet LINK and ETH here: https://faucets.chain.link/
13-
* Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
14-
*/
15-
16-
contract RandomNumberConsumer is VRFConsumerBase {
17-
18-
bytes32 internal keyHash;
19-
uint256 internal fee;
20-
21-
uint256 public randomResult;
13+
VRFCoordinatorV2Interface COORDINATOR;
14+
LinkTokenInterface LINKTOKEN;
2215

23-
event RequestId(address, bytes32);
24-
event FulfillRandomness(bytes32, uint256);
16+
// Your subscription ID.
17+
uint64 s_subscriptionId;
2518

26-
/**
27-
* Constructor inherits VRFConsumerBase
28-
*
29-
* Network: Kovan
30-
* Chainlink VRF Coordinator address: 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9
31-
* LINK token address: 0xa36085F69e2889c224210F603D836748e7dC0088
32-
* Key Hash: 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4
33-
*/
34-
constructor()
35-
VRFConsumerBase(
36-
0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator
37-
0xa36085F69e2889c224210F603D836748e7dC0088 // LINK Token
38-
)
39-
{
40-
// KeyHash see this link: https://docs.chain.link/docs/vrf-contracts/
41-
keyHash = 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4;
42-
fee = 0.1 * 10 ** 18; // 0.1 LINK (Varies by network)
19+
// Rinkeby coordinator. For other networks,
20+
// see https://docs.chain.link/docs/vrf-contracts/#configurations
21+
address vrfCoordinator = 0x6168499c0cFfCaCD319c818142124B7A15E857ab;
22+
23+
// Rinkeby LINK token contract. For other networks,
24+
// see https://docs.chain.link/docs/vrf-contracts/#configurations
25+
address link = 0x01BE23585060835E02B77ef475b0Cc51aA1e0709;
26+
27+
// The gas lane to use, which specifies the maximum gas price to bump to.
28+
// For a list of available gas lanes on each network,
29+
// see https://docs.chain.link/docs/vrf-contracts/#configurations
30+
bytes32 keyHash = 0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc;
31+
32+
// Depends on the number of requested values that you want sent to the
33+
// fulfillRandomWords() function. Storing each word costs about 20,000 gas,
34+
// so 100,000 is a safe default for this example contract. Test and adjust
35+
// this limit based on the network that you select, the size of the request,
36+
// and the processing of the callback request in the fulfillRandomWords()
37+
// function.
38+
uint32 callbackGasLimit = 100000;
39+
40+
// The default is 3, but you can set this higher.
41+
uint16 requestConfirmations = 3;
42+
43+
// For this example, retrieve 2 random values in one request.
44+
// Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
45+
uint32 numWords = 2;
46+
47+
uint256[] public s_randomWords;
48+
uint256 public s_requestId;
49+
address s_owner;
50+
51+
constructor(uint64 subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
52+
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
53+
LINKTOKEN = LinkTokenInterface(link);
54+
s_owner = msg.sender;
55+
s_subscriptionId = subscriptionId;
4356
}
44-
45-
/**
46-
* Requests randomness
47-
*/
48-
function getRandomNumber() public returns (bytes32 requestId) {
49-
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
50-
requestId = requestRandomness(keyHash, fee);
51-
emit RequestId(msg.sender, requestId);
52-
return requestId;
57+
58+
// Assumes the subscription is funded sufficiently.
59+
function requestRandomWords() external onlyOwner {
60+
// Will revert if subscription is not set and funded.
61+
s_requestId = COORDINATOR.requestRandomWords(keyHash, s_subscriptionId, requestConfirmations, callbackGasLimit, numWords);
62+
emit RequestId(msg.sender,s_requestId);
5363
}
5464

55-
/**
56-
* Callback function used by VRF Coordinator
57-
*/
58-
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
59-
randomResult = randomness;
60-
emit FulfillRandomness(requestId, randomness);
61-
// If your fulfillRandomness function uses more than 200k gas, the transaction will fail.
62-
// do something you want, like mint NFT...
65+
function fulfillRandomWords(
66+
uint256, /* requestId */
67+
uint256[] memory randomWords
68+
) internal override {
69+
s_randomWords = randomWords;
70+
emit FulfillRandomness(s_requestId,s_randomWords);
6371
}
6472

65-
// function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
73+
modifier onlyOwner() {
74+
require(msg.sender == s_owner);
75+
_;
76+
}
6677
}

basic/14-chainlink-price-feed/hardhat.config.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ require("@nomiclabs/hardhat-waffle")
22
require("@nomiclabs/hardhat-ethers")
33
require("@nomiclabs/hardhat-web3")
44
require("@nomiclabs/hardhat-truffle5")
5+
require("@nomiclabs/hardhat-etherscan");
56
require("hardhat-deploy")
67
const fs = require("fs");
78
require('dotenv').config()
@@ -28,8 +29,6 @@ function mnemonic() {
2829
/**
2930
* @type import('hardhat/config').HardhatUserConfig
3031
*/
31-
const kovanRpcUrl = "https://kovan.infura.io/v3/" + process.env.INFURA_ID
32-
const privateKey = mnemonic()
3332

3433
module.exports = {
3534
defaultNetwork: "hardhat",
@@ -41,10 +40,13 @@ module.exports = {
4140
// }
4241
},
4342
kovan: {
44-
url: kovanRpcUrl,
45-
accounts: [privateKey],
46-
saveDeployments: true
47-
}
43+
url: 'https://kovan.infura.io/v3/' + process.env.INFURA_ID, //<---- YOUR INFURA ID! (or it won't work)
44+
accounts: [mnemonic()],
45+
},
46+
rinkeby: {
47+
url: 'https://rinkeby.infura.io/v3/' + process.env.INFURA_ID, //<---- YOUR INFURA ID! (or it won't work)
48+
accounts: [mnemonic()],
49+
},
4850
},
4951
namedAccounts: {
5052
deployer: {
@@ -62,8 +64,11 @@ module.exports = {
6264
}
6365
]
6466
},
67+
etherscan: {
68+
apiKey: "HT789QWQ9RXQQ86K3HP4EAVTEWYVKKPPJ9"
69+
},
6570
mocha: {
66-
timeout: 600000
71+
timeout: 6000000000000000
6772
}
6873
}
6974

basic/14-chainlink-price-feed/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"author": "",
1111
"license": "ISC",
1212
"devDependencies": {
13-
"@chainlink/contracts": "0.3.0",
1413
"@nomiclabs/hardhat-ethers": "^2.0.2",
1514
"@nomiclabs/hardhat-truffle5": "^2.0.0",
1615
"@nomiclabs/hardhat-waffle": "^2.0.1",
@@ -23,6 +22,8 @@
2322
"web3": "^1.3.6"
2423
},
2524
"dependencies": {
25+
"@chainlink/contracts": "^0.4.0",
26+
"@nomiclabs/hardhat-etherscan": "^3.0.1",
2627
"dotenv": "^10.0.0"
2728
}
2829
}

basic/14-chainlink-price-feed/scripts/02-RandomNumberConsumerDeploy.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const hre = require('hardhat');
22
require('@nomiclabs/hardhat-web3');
3+
require('dotenv').config();
34

45
async function main() {
56
const [deployer] = await ethers.getSigners();
@@ -8,7 +9,7 @@ async function main() {
89

910
// 部署 RandomNumberConsumer 合约
1011
const RandomNumberConsumer = await ethers.getContractFactory('RandomNumberConsumer');
11-
const instance = await RandomNumberConsumer.deploy();
12+
const instance = await RandomNumberConsumer.deploy(process.env.SubscriptionId);
1213
await instance.deployed();
1314

1415
console.log('----------------------------------------------------');
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
const hre = require('hardhat');
2+
require('@nomiclabs/hardhat-web3');
3+
const { BigNumber } = require('ethers');
4+
require('dotenv').config();
5+
6+
async function main() {
7+
const provider = new ethers.providers.WebSocketProvider(`wss://rinkeby.infura.io/ws/v3/${process.env.INFURA_ID}`);
8+
const { abi: RandomNumberConsumerABI } = require('../artifacts/contracts/RandomNumberConsumer.sol/RandomNumberConsumer.json');
9+
10+
const addr = process.env.RandomNumberConsumer_ADDRESS; // <--- you need fill it in .env file
11+
if (!addr) {
12+
console.log('Please set the contract address in .env file.');
13+
return;
14+
}
15+
16+
let randomNumberConsumer, user1, iface;
17+
18+
randomNumberConsumer = new ethers.Contract(addr, RandomNumberConsumerABI, provider);
19+
iface = new ethers.utils.Interface(RandomNumberConsumerABI);
20+
[user1] = await ethers.getSigners();
21+
22+
let random0ID, random0Res;
23+
24+
// 监听randomNumberConsumer 的请求随机数事件
25+
const filterCall = {
26+
address: addr,
27+
topics: [ethers.utils.id('RequestId(address,uint256)')],
28+
};
29+
// 监听chainlink VRF Coordinator 的随机数回写事件
30+
const filterRes = {
31+
address: addr,
32+
topics: [ethers.utils.id('FulfillRandomness(uint256,uint256[])')],
33+
};
34+
35+
console.log(`Listen on random number call...`);
36+
provider.on(filterCall, (log, event) => {
37+
console.log('event RequestId(address,uint256)');
38+
const { args } = iface.parseLog(log);
39+
if (args[0] === user1.address) {
40+
random0ID = args[1];
41+
console.log('random0 requestID: ', random0ID);
42+
} else {
43+
console.log('msg.sender not matched.');
44+
}
45+
});
46+
47+
console.log(`Listen on random number result...`);
48+
provider.on(filterRes, (log, event) => {
49+
console.log('event FulfillRandomness(uint256,uint256[])');
50+
const { args } = iface.parseLog(log);
51+
console.log("args[0] :",args[0]);
52+
if (BigNumber.from(args[0]).eq(random0ID)) {
53+
random0Res = args[1];
54+
console.log('random0Res: ', random0Res.toString());
55+
} else {
56+
console.log('requestID not matched.');
57+
}
58+
});
59+
60+
const tx0 = await randomNumberConsumer.connect(user1).requestRandomWords({
61+
// for sometimes , it will be fail if not specify the gasLimit
62+
gasLimit: 200000
63+
});
64+
console.log('first transaction hash:', tx0.hash);
65+
66+
// wait for the result event
67+
for (let i = 0; i < 500; i++) {
68+
if (random0Res) break;
69+
console.log("Please be patient, it will take sometime to get the result")
70+
await new Promise((resolve) => {
71+
setTimeout(() => {
72+
resolve();
73+
}, 1000);
74+
});
75+
}
76+
}
77+
78+
// We recommend this pattern to be able to use async/await everywhere
79+
// and properly handle errors.
80+
main()
81+
.then(() => process.exit(0))
82+
.catch((error) => {
83+
console.error(error);
84+
process.exit(1);
85+
});

0 commit comments

Comments
 (0)