今天体验了一下ChainLink的VRF功能,用的是社区的一个模拟彩票抽奖的智能合约。下面是智能合约的主要代码:
RandomNumberGenerator.sol
pragma solidity ^0.6.2;
import "./VRFConsumerBase.sol";
import "./Lottery.sol";
contract RandomNumberGenerator is VRFConsumerBase {
address requester;
bytes32 keyHash;
uint256 fee;
constructor(address _vrfCoordinator, address _link, bytes32 _keyHash, uint256 _fee)
VRFConsumerBase(_vrfCoordinator, _link) public {
keyHash = _keyHash;
fee = _fee;
}
function fulfillRandomness(bytes32 _requestId, uint256 _randomness) external override {
Lottery(requester).numberDrawn(_requestId, _randomness);
}
function request(uint256 _seed) public returns(bytes32 requestId) {
require(keyHash != bytes32(0), "Must have valid key hash");
requester = msg.sender;
return this.requestRandomness(keyHash, fee, _seed);
}
}
Lottery.sol
pragma solidity >=0.6.2;
import "openzeppelin/contracts/access/Ownable.sol";
import "openzeppelin/contracts/utils/EnumerableSet.sol";
import "openzeppelin/contracts/utils/Address.sol";
import "openzeppelin/contracts/math/SafeMath.sol";
import "./RandomNumberGenerator.sol";
contract Lottery is Ownable{
using EnumerableSet for EnumerableSet.AddressSet;
using Address for address;
using SafeMath for uint;
enum LotteryState { Open, Closed, Finished }
mapping(uint => EnumerableSet.AddressSet) entries;
uint[] numbers;
LotteryState public state;
uint public numberOfEntries;
uint public entryFee;
uint public ownerCut;
uint public winningNumber;
address randomNumberGenerator;
bytes32 randomNumberRequestId;
event LotteryStateChanged(LotteryState newState);
event NewEntry(address player, uint number);
event NumberRequested(bytes32 requestId);
event NumberDrawn(bytes32 requestId, uint winningNumber);
// modifiers
modifier isState(LotteryState _state) {
require(state == _state, "Wrong state for this action");
_;
}
modifier onlyRandomGenerator {
require(msg.sender == randomNumberGenerator, "Must be correct generator");
_;
}
//constructor
constructor (uint _entryFee, uint _ownerCut, address _randomNumberGenerator) public Ownable() {
require(_entryFee > 0, "Entry fee must be greater than 0");
require(_ownerCut < _entryFee, "Entry fee must be greater than owner cut");
require(_randomNumberGenerator != address(0), "Random number generator must be valid address");
require(_randomNumberGenerator.isContract(), "Random number generator must be smart contract");
entryFee = _entryFee;
ownerCut = _ownerCut;
randomNumberGenerator = _randomNumberGenerator;
_changeState(LotteryState.Open);
}
//functions
function submitNumber(uint _number) public payable isState(LotteryState.Open) {
require(msg.value >= entryFee, "Minimum entry fee required");
require(entries[_number].add(msg.sender), "Cannot submit the same number more than once");
numbers.push(_number);
numberOfEntries++;
payable(owner()).transfer(ownerCut);
emit NewEntry(msg.sender, _number);
}
function drawNumber(uint256 _seed) public onlyOwner isState(LotteryState.Open) {
_changeState(LotteryState.Closed);
randomNumberRequestId = RandomNumberGenerator(randomNumberGenerator).request(_seed);
emit NumberRequested(randomNumberRequestId);
}
function rollover() public onlyOwner isState(LotteryState.Finished) {
//rollover new lottery
}
function numberDrawn(bytes32 _randomNumberRequestId, uint _randomNumber) public onlyRandomGenerator isState(LotteryState.Closed) {
if (_randomNumberRequestId == randomNumberRequestId) {
winningNumber = _randomNumber;
emit NumberDrawn(_randomNumberRequestId, _randomNumber);
_payout(entries[_randomNumber]);
_changeState(LotteryState.Finished);
}
}
function _payout(EnumerableSet.AddressSet storage winners) private {
uint balance = address(this).balance;
for (uint index = 0; index < winners.length(); index++) {
payable(winners.at(index)).transfer(balance.div(winners.length()));
}
}
function _changeState(LotteryState _newState) private {
state = _newState;
emit LotteryStateChanged(state);
}
}
部署RandomNumberGenerator合约
我打算在ropsten测试网上部署所有的合约
这四个参数均来自ChainLink官网资料。
VRF Coordinator 0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb
Key Hash 0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205
Fee 1 LINK
LINK 代币: 0x20fE562d797A42Dcb3399062AE9546cd06f63280
这里要注意的是,部署好这个合约之后一定要给这个合约转账大于1LINK的代币。因为每次抽奖都要消耗1LINK。
部署Lottery合约
这里最后一项是上面部署的RandomNumberGenerator合约的地址,_entryfee是指买彩票的费用,_ownercut是指合约拥有者要收取的佣金。
买彩票,选数字
这里要注意的是,在调用submitNumber智能合约接口的时候,一定不要忘了设置购买彩票的费用,比如我这里给的是0.2,实际上,只需要给0.001eth就行了。但因为我是测试网上的eth,管够,所以懒得敲那么多0。
开奖
这里的_seed是自由选择的,我就随便选了一个交易hash做为它的值。
执行完这个交易后,等一段时间,查询合约的状态和中奖号码。
可以看到合约状态变成1了,1表示close。
中奖号码也出来了,虽然大的离谱,但效果已经有了。
(全文完)
参考资料:
https://cloud.tencent.com/developer/article/1634665
https://mp.weixin.qq.com/s/vTq4aiznIMkaY9SvzEYufw