LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 币圈百科 > 区块链研究实验室|在以太坊上建立可验证的随机彩票智能合约

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

2020-06-05 陀螺财经 来源:火星财经

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

以太坊生态系统中最大的参与者之一的最新声明引起了对此问题的兴奋。使用称为可验证随机函数(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}

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

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

扫码

关注我们

获得

本文来源:陀螺财经
原文标题:区块链研究实验室|在以太坊上建立可验证的随机彩票智能合约

—-

编译者/作者:陀螺财经

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

知识 随机数
LOADING...
LOADING...