在鏈上創建隨機數是一項複雜的任務。事實上,有一些方法可以做到這一點,但總的來說,強烈建議在鏈下進行,因為幾乎所有用於熵的輸入都是公開的,或者在某種程度上可以被操縱。

幸運的是,這個挑戰要求我們猜測鏈上創建的一個“隨機”數字。這是怎麼回事?

猜隨機數字挑戰智能合約代碼

合約的第一行是一個uint8變量,answer。記住uint8變量最多包含256個可能的整數:0到255。

這個變量在構造函數中被分配給兩個輸入的keccak256 哈希:包含我們部署交易的那個區塊的前一個區塊的blockhash(block.blockhash(block.number - 1), of type bytes32)和我們的區塊被挖的時間戳(now, of type uint256)。

請記住,這個合約使用的是編譯器版本^0.4.21,從那以後,一些語法發生了變化:block.blockhash()現在是blockhash(),now現在是block.timestamp。

正如我們在這行中看到的,keccak256函數(一個bytes32 固定大小的字節數組)隨後被顯式轉換為uint8並賦值給我們的變量。

這看起來很隨機,對吧?我們應該如何猜出0到255之間的一個數字,它來自於對某個區塊的哈希函數和時間戳。

其實很簡單。因為區塊鏈上的所有東西都是公開的。

我們的目標是在函數下guess,我們必須調用它並發送一個uint8 + 1 以太(我們已經在部署上發送了一個),然後如果我們的uint8等於answer變量,合約將發送我們2個以太,耗盡餘額,因此isComplete()函數將返回到true。

有多種與合約交互的方式,但我決定通過另一個合約來實現。這不是最簡單的方法,在這種情況下,甚至沒有必要,但絕對是我們可以利用的方法。

以下是我為解決這個問題所編寫的代碼:

// SPDX-License-Identifier: No Licensepragma solidity ^0.8.0;interface IGuessTheRandomNumberChallenge { function guess(uint8) external payable;}contract GuessTheRandomNumberSolver { IGuessTheRandomNumberChallenge public _interface; bytes32 public previousBlockHash = 0x66bcdb5e320c9e0c04a9fdeaa15de33a4c8a040db342f4f955fa54f170dba9ce; uint public previousTimestamp = 1641520092; constructor(address _interfaceAddress) { require(_interfaceAddress != address(0), 'Address can not be Zero'); _interface = IGuessTheRandomNumberChallenge(_interfaceAddress); } function solve() public payable { uint8 answer = uint8(uint256(keccak256(abi.encodePacked( previousBlockHash, previousTimestamp)))); _interface.guess{value: 1 ether}(answer); } function getBalance() public view returns(uint){ return address(this).balance; } function withdraw() public { payable( msg.sender).transfer(address(this).balance); } receive() external payable {}}

編譯器版本之後,首先看到的是一個接口。我們可以使用它們通過代碼與其他合約交互。它基本上是一個帶有一些規則的簡單合約:

它們不能從其他合約繼承,但可以從其他接口繼承。所有聲明的函數必須是外部的。它們不能聲明構造函數。它們不能聲明狀態變量。它們不能聲明修飾符。

因為我們只需要調用' guess '函數,所以它是我們在接口中聲明的唯一一個函數。

然後,在我們的GuessTheRandomNumberSolver合約中,我們將聲明一個_interface變量,並通過構造函數分配挑戰的地址(在CTE中部署它時獲得的的地址)。

這就是我們現在在已部署的挑戰中調用函數所需要的一切,我們繼續收集信息,以重新創建與它一起部署的random number。

這些都可以在etherscan中找到,我們只需要尋找我們挑戰的地址。

Blockhash(block.number - 1):要得到這個,可以轉到內部Internal Txns標籤,然後單擊顯示Contract Creation的同一行上的區塊號碼。在我的例子中,區塊是#11766860:

現在,我們可以看到很多關於那個區塊的信息,但我們需要訪問前一個,所以繼續尋找它。在我的例子中,它是#11766859。

下面我們可以看到hash。這是我們需要的第一個信息。

Block.timestamp:回到我們的區塊,你會在第二行看到時間戳。這是一種人類可讀的格式,我們需要Unix Timestamp格式。那是什麼? 它是自1970年1月1日以來所經過的秒數。這是衡量時間的標準方法。

為了將這個人類可讀的時間戳轉換為Unix時間,我使用了一個非常方便的站點epochconverter。有了這個數字,我們終於有了最後一塊拼圖,我們可以來解決這個挑戰。

回到GuessTheRandomNumberSolver合約,讓我們創建一個solve函數,我們將調用它來聯繫我們的挑戰合約。

為了提高可讀性,我還創建了兩個新變量:

bytes32 public previousBlockHashuint public previousTimestamp.

創建它們,但要賦予它們挑戰的價值。

然後,在我們的solve函數中,我們將創建uint8 answer變量,並將其賦值:

uint8(uint256(keccak256(abi.encodePacked(previousBlockHash, previousTimestamp))))

語法和格式的變化是因為我們使用的是^0.8.0版本的編譯器,而挑戰是使用^0.4.21版本。

現在我們已經將答案賦給了變量,我們只需要通過接口調用挑戰。這就是下一行要做的:

_interface.guess{value: 1 ether}(answer)

我假設你正在使用remix,所以繼續,通過Injected Web3環境連接到metamask錢包,並部署合約,指定自己的挑戰地址來分配給自己的界面。

現在,在將值輸入為1的情況下,繼續調用guess函數。

我已經添加了更多的函數:

getBalance()withdraw()receive()

這是因為挑戰是msg.sender將是我們的GuessTheRandomNumberSolver合約,而不是我們的EOA -所以我們需要接收2個以太,並能夠將它們發送到我們的EOA。

Source:https://betterprogramming.pub/capture-the-ether-guess-the-random-number-2ebb8c9c0347