區塊鏈研究實驗室|在以太坊上建立可驗證的隨機彩票智慧合約

買賣虛擬貨幣

在以太坊上,真正的隨機性幾乎是不可能的。這是因為事務需要由網路上的多個節點進行驗證才能確認。如果智慧合約功能確實是隨機的,那麼使用該功能驗證交易的每個節點將得出不同的結果,這意味著該交易將永遠不會被確認。

以太坊生態系統中最大的參與者之一的最新宣告引起了對此問題的興奮。使用稱為可驗證隨機函式(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}

這是一個原始的實現,但是它顯示了可驗證的隨機性在區塊鏈上的出現如何降低了彩票之類的合約的複雜性。以前的彩票合約需要使用雜湊機制,基於時間的機制,基於區塊的機制等,所有這些都容易受到攻擊。

描下方二維碼新增我,拉您進入技術交流群

掃碼

關注我們

獲得

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;