区块链研究实验室|在以太坊上建立可验证的随机彩票智能合约

  • 时间:
  • 浏览:46
  • 来源:区块链技术网

在以太坊上,真正的随机性几乎是不可能的。这是因为事务需要由网络上的多个节点进行验证才能确认。如果智能合约功能确实是随机的,那么使用该功能验证交易的每个节点将得出不同的结果,这意味着该交易将永远不会被确认。

以太坊生态系统中最大的参与者之一的最新声明引起了对此问题的兴奋。使用称为可验证随机函数(VRF)的系统,以太坊智能合约现在可以生成随机数。

这意味着,那些看似与智能合约完美契合,但却无法实现的概念,因为它们现在需要随机数。

其中一个概念是彩票。

建立彩票智能合约

我们的彩票有三个阶段。第一种是开放式,任何人都可以提交新的号码,只需支付少量费用。第二个是关闭的,没有新的数字可以提交,随机数正在生成。第三个已经完成,号码已经生成,赢家已经获得奖励。

如果没有人中奖,可以将彩票合约延期,从而增加头奖筹码。

定义阶段

阶段应限制操作,以便只能执行允许的操作。例如应该允许新提交的唯一阶段是开放阶段。如果彩票关闭或结束,合同应禁止新的提交。

使用enum,我们可以定义任意多个阶段。我们称它为LotteryState。在状态变量中,我们定义以下内容:

1enumLotteryState{Open,Closed,Finished}2LotteryStatepublicstate;

现在已经定义了枚举,我们可以在函数中设置规则(require语句),以确保合约的当前状态符合我们的期望。

鉴于这些require声明可能在整个合约中看起来都相似,所以我们将其最小化。我们可以定义一个执行require语句的修饰符,并将其分配给我们想要的任何函数。

1modifierisState(LotteryState_state){2require(state==_state,"Wrongstateforthisaction");3_;4}

现在当我们定义函数时,我们可以添加此修饰符以确保彩票的当前状态是我们期望的状态。

提交数字

只要支付了最低入场费,任何人都可以提交号码。但是每个参赛者不能一次提交同一号码。应该允许新提交的唯一状态是打开状态。

这是我们的SubmitNumber函数:

1functionsubmitNumber(uint_number)publicpayableisState(LotteryState.Open){2require(msg.value>=entryFee,"Minimumentryfeerequired");3require(entries[_number].add(msg.sender),"Cannotsubmitthesamenumbermorethanonce");4numbers.push(_number);5numberOfEntries++;6payable(owner()).transfer(ownerCut);7emitNewEntry(msg.sender,_number);8}

第1行定义了名称,单个_number参数以及它是public的和payable的事实。它还添加了isState修饰符,以确保彩票是开放的。

第2行确保已支付正确的报名费,第3行确保消息的发件人尚未提交该号码,并将其添加到流程中的条目中。

变量entries引用了一个映射,该映射定义了猜测的数字和已输入该数字的一组地址。定义如下:

1mapping(uint=>EnumerableSet.AddressSet)entries;

AddressSet引用OpenZeppelin EnumerableSet协定,该协定为原始类型提供附加函数。

一旦检查完成,接下来的四行将数字添加到猜测中,支付所有者削减的一小部分,并发出NewEntry事件。

输入数字

如果您已经阅读了有关如何使用VRF的文章,那么您将知道生成随机数并不像调用单个函数那样简单(例如JavaScript中的Math.random())。

要生成随机数,必须从VRF协调器请求随机性,并实现VRF可以在响应中回调的功能。为此我们需要定义一个VRF使用者(可在此处找到创建VRF使用者的详细信息),在图2中将其称为RandomNumberGenerator。

1pragmasolidity^0.6.2; 2 3import"./VRFConsumerBase.sol"; 4import"./Lottery.sol"; 5 6contractRandomNumberGeneratorisVRFConsumerBase{ 7 8addressrequester; 9bytes32keyHash;10uint256fee;1112constructor(address_vrfCoordinator,address_link,bytes32_keyHash,uint256_fee)13VRFConsumerBase(_vrfCoordinator,_link)public{14keyHash=_keyHash;15fee=_fee;16}1718functionfulfillRandomness(bytes32_requestId,uint256_randomness)externaloverride{19Lottery(requester).numberDrawn(_requestId,_randomness);20}2122functionrequest(uint256_seed)publicreturns(bytes32requestId){23require(keyHash!=bytes32(0),"Musthavevalidkeyhash");24requester=msg.sender;25returnthis.requestRandomness(keyHash,fee,_seed);26}27}

我们的彩票将在构建时将此合同的地址作为注入参数。绘制数字时,它将调用请求函数。这要求VRF提供随机性,然后VRF向第18行的filfullRandomness提供响应。您可以在图2中看到调用,它调用了我们的numberDrawn彩票合约。让我们定义这些功能:

1functiondrawNumber(uint256_seed)publiconlyOwnerisState(LotteryState.Open){ 2_changeState(LotteryState.Closed); 3randomNumberRequestId=RandomNumberGenerator(randomNumberGenerator).request(_seed); 4emitNumberRequested(randomNumberRequestId); 5} 6 7functionnumberDrawn(bytes32_randomNumberRequestId,uint_randomNumber)publiconlyRandomGeneratorisState(LotteryState.Closed){ 8if(_randomNumberRequestId==randomNumberRequestId){ 9winningNumber=_randomNumber;10emitNumberDrawn(_randomNumberRequestId,_randomNumber);11_payout(entries[_randomNumber]);12_changeState(LotteryState.Finished);13}14}

在我们的定义的第1行中,只能由彩票所有者调用drawNumber,并且只能在彩票处于打开状态时调用。

第7行上的numberDrawn是一旦VRF接收到随机数后,complementRandomness会回调的函数。它确保request-id是从请求返回的ID,发出事件,支付中奖者并将彩票的状态更改为Finished。

完整代码展示:

1pragmasolidity>=0.6.2; 2 3import"@openzeppelin/contracts/access/Ownable.sol"; 4import"@openzeppelin/contracts/utils/EnumerableSet.sol"; 5import"@openzeppelin/contracts/utils/Address.sol"; 6import"@openzeppelin/contracts/math/SafeMath.sol"; 7import"./RandomNumberGenerator.sol"; 8 9contractLotteryisOwnable{1011usingEnumerableSetforEnumerableSet.AddressSet;12usingAddressforaddress;13usingSafeMathforuint;1415enumLotteryState{Open,Closed,Finished}1617mapping(uint=>EnumerableSet.AddressSet)entries;18uint[]numbers;19LotteryStatepublicstate;20uintpublicnumberOfEntries;21uintpublicentryFee;22uintpublicownerCut;23uintpublicwinningNumber;24addressrandomNumberGenerator;25bytes32randomNumberRequestId;2627eventLotteryStateChanged(LotteryStatenewState);28eventNewEntry(addressplayer,uintnumber);29eventNumberRequested(bytes32requestId);30eventNumberDrawn(bytes32requestId,uintwinningNumber);3132//modifiers33modifierisState(LotteryState_state){34require(state==_state,"Wrongstateforthisaction");35_;36}3738modifieronlyRandomGenerator{39require(msg.sender==randomNumberGenerator,"Mustbecorrectgenerator");40_;41}4243//constructor44constructor(uint_entryFee,uint_ownerCut,address_randomNumberGenerator)publicOwnable(){45require(_entryFee>0,"Entryfeemustbegreaterthan0");46require(_ownerCut<_entryFee,"Entryfeemustbegreaterthanownercut");47require(_randomNumberGenerator!=address(0),"Randomnumbergeneratormustbevalidaddress");48require(_randomNumberGenerator.isContract(),"Randomnumbergeneratormustbesmartcontract");49entryFee=_entryFee;50ownerCut=_ownerCut;51randomNumberGenerator=_randomNumberGenerator;52_changeState(LotteryState.Open);53}5455//functions56functionsubmitNumber(uint_number)publicpayableisState(LotteryState.Open){57require(msg.value>=entryFee,"Minimumentryfeerequired");58require(entries[_number].add(msg.sender),"Cannotsubmitthesamenumbermorethanonce");59numbers.push(_number);60numberOfEntries++;61payable(owner()).transfer(ownerCut);62emitNewEntry(msg.sender,_number);63}6465functiondrawNumber(uint256_seed)publiconlyOwnerisState(LotteryState.Open){66_changeState(LotteryState.Closed);67randomNumberRequestId=RandomNumberGenerator(randomNumberGenerator).request(_seed);68emitNumberRequested(randomNumberRequestId);69}7071functionrollover()publiconlyOwnerisState(LotteryState.Finished){72//rollovernewlottery73}7475functionnumberDrawn(bytes32_randomNumberRequestId,uint_randomNumber)publiconlyRandomGeneratorisState(LotteryState.Closed){76if(_randomNumberRequestId==randomNumberRequestId){77winningNumber=_randomNumber;78emitNumberDrawn(_randomNumberRequestId,_randomNumber);79_payout(entries[_randomNumber]);80_changeState(LotteryState.Finished);81}82}8384function_payout(EnumerableSet.AddressSetstoragewinners)private{85uintbalance=address(this).balance;86for(uintindex=0;index<winners.length();index++){87payable(winners.at(index)).transfer(balance.div(winners.length()));88}89}9091function_changeState(LotteryState_newState)private{92state=_newState;93emitLotteryStateChanged(state);94}95}

这是一个原始的实现,但是它显示了可验证的随机性在区块链上的出现如何降低了彩票之类的合约的复杂性。以前的彩票合约需要使用哈希机制,基于时间的机制,基于区块的机制等,所有这些都容易受到攻击。

描下方二维码添加我,拉您进入技术交流群

扫码

关注我们

获得

猜你喜欢

如何使用VRF在以太坊上生成随机数

原作者:AlexRoan随机数和区块链一直存在分歧。到目前为止,区块链上还没有可验证的随机函数。问题源于以下事实:交易被挖掘时,需要由网络上的多个节点来确认。这意味着每个节点都

2021-11-29

[译]区块链上生成随机数

在区块链上如何生成随机数。![PhotobyLaunchpressoonUnsplash](https://img.learnblockchain.cn/2020/04/11/

2021-11-28

如何使用VRF(可验证随机函数)在以太坊上生成随机数

Chainlink如何解决以太坊“随机数问题”随机数和区块链一直很难达到“一致”(译者注:区块链要求确定性,而随机数正相反)。到目前为止,区块链上还没有可验证的随机函数。原因是

2021-11-28

在以太坊生成随机数的几种方式(含代码)

在以太坊上使用的随机数来源主要有链上和链下两种途径,其中链上生成核心要解决随机数生成种子的不可预测性。#一、什么是随机数随机数都是由随机数生成器(RandomNumberGen

2021-11-28

随机数与区块链

如何实现Solidity智能合约的安全随机数?>*原文:https://soliditydeveloper.com/2019-06-23-randomness-block

2021-11-28