我們的目標是在函數下guess。
在鏈上創建隨機數是一項複雜的任務。事實上,有一些方法可以做到這一點,但總的來說,強烈建議在鏈下進行,因為幾乎所有用於熵的輸入都是公開的,或者在某種程度上可以被操縱。
幸運的是,這個挑戰要求我們猜測鏈上創建的一個“隨機”數字。這是怎麼回事?
猜隨機數字挑戰智能合約代碼合約的第一行是一個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: 無許可證
pragma solidity ^0.8.0;interface IGuessTheRandomNumberChallenge {
函數guess(uint8) 外部應付;
}合約 GuessTheRandomNumberSolver { IGuessTheRandomNumberChallenge public _interface;
bytes32 public previousBlockHash = 0x66bcdb5e320c9e0c04a9fdeaa15de33a4c8a040db342f4f955fa54f170dba9ce;
uint public previousTimestamp = 1641520092; 構造函數(地址_interfaceAddress){
require(_interfaceAddress != address(0), “地址不能為零”);
_interface = IGuessTheRandomNumberChallenge(_interfaceAddress);
} 函數solve() 公共支付{
uint8 答案 = uint8(uint256(keccak256(abi.encodePacked(previousBlockHash, previousTimestamp))));
_interface.guess{value: 1 ether}(answer);
} function getBalance() 公共視圖返回(uint){
返回地址(this).balance;
} 功能撤回()公共{
應付(msg.sender).transfer(地址(this).balance);
} receive() 外部應付款 {}
}
編譯器版本之後,首先看到的是一個接口。我們可以使用它們通過代碼與其他合約交互。它基本上是一個帶有一些規則的簡單合約:
-
它們不能從其他合約繼承,但可以從其他接口繼承。
-
所有聲明的函數必須是外部的。
-
它們不能聲明構造函數。
-
它們不能聲明狀態變量。
-
它們不能聲明修飾符。
因為我們只需要調用’ 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 previousBlockHash
-
uint public previousTimestamp。
創建它們,但要賦予它們挑戰的價值。
然後,在我們的solve函數中,我們將創建uint8 answer變量,並將其賦值:
uint8(uint256(keccak256(abi.encodePacked(previousBlockHash, previousTimestamp))))
語法和格式的變化是因為我們使用的是^0.8.0版本的編譯器,而挑戰是使用^0.4.21版本。
現在我們已經將答案賦給了變量,我們只需要通過接口調用挑戰。這就是下一行要做的:
_interface.guess{value: 1 ether}(答案)
我假設你正在使用remix,所以繼續,通過Injected Web3環境連接到metamask錢包,並部署合約,指定自己的挑戰地址來分配給自己的界面。
現在,在將值輸入為1的情況下,繼續調用guess函數。
我已經添加了更多的函數:
-
獲得平衡()
-
提取()
-
收到()
這是因為挑戰是msg.sender將是我們的GuessTheRandomNumberSolver合約,而不是我們的EOA -所以我們需要接收2個以太,並能夠將它們發送到我們的EOA。
來源:https://betterprogramming.pub/capture-the-ether-guess-the-random-number-2ebb8c9c0347
展開全文打開碳鏈價值APP 查看更多精彩資訊