Используется ли block.timestamp для операций, чувствительных ко времени?
Майнеры могут манипулировать block.timestamp на несколько секунд, что может повлиять на логику контрактов, зависящих от времени.
Как предотвратить: используйте block.number вместо временных меток для критически важных операций, связанных со временем, или убедитесь, что допустимая погрешность манипуляций приемлема.
block.timestamp представляет время, которое майнер предлагает для блока. Хотя оно обычно точное, майнеры имеют некоторую свободу для его корректировки. Эта свобода допускает потенциальные уязвимости в логике, чувствительной ко времени, такой как аукционы или периоды стейкинга.
Эта уязвимость является прямым следствием манипуляций майнеров, отражая их способность влиять на создание блоков. Хотя майнеры не могут устанавливать произвольные временные метки, они имеют некоторый контроль в рамках правил консенсуса. В Ethereum это примерно +/- 900 секунд (15 минут), хотя точный диапазон может варьироваться в зависимости от блокчейна.
Представьте себе аукцион, на котором майнер сдвигает временную метку в пользу своей ставки или ставки своего союзника. Это может привести к несправедливым преимуществам, таким как преждевременное завершение аукциона или манипулирование процессом торгов.
Посмотрите на пример:
pragma solidity ^0.8.0;
// SPDX-License-Identifier: UNLICENSED
contract Auction {
uint public auctionEndTime;
address public highestBidder;
uint public highestBid;
mapping(address => uint) public pendingReturns;
bool public ended;
event BidPlaced(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
constructor(uint _duration) {
auctionEndTime = block.timestamp + _duration; // Vulnerability!
}
function isAuctionEnded() public view returns (bool) {
return block.timestamp >= auctionEndTime; // Vulnerable comparison!
}
function bid() public payable {
require(!isAuctionEnded(), "Auction has ended");
require(msg.value > highestBid, "Bid not high enough");
if (highestBid > 0) {
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit BidPlaced(msg.sender, msg.value);
}
function endAuction() public {
require(!ended, "Auction already ended");
require(isAuctionEnded(), "Auction not yet ended");
ended = true;
emit AuctionEnded(highestBidder, highestBid);
}
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
// Use call instead of transfer for better compatibility
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
}
return true;
}
}
В этом аукционном контракте время окончания аукциона (auctionEndTime) определяется как block.timestamp + _duration. Майнер может слегка изменить block.timestamp, чтобы досрочно завершить аукцион или отложить его. Он может отложить его настолько, чтобы его ставка была включена в блок, или ускорить его, чтобы исключить конкурирующую ставку.
Вместо того, чтобы полагаться непосредственно на block.timestamp, рассмотрите возможность использования block.number.
pragma solidity ^0.8.0;
contract FixedAuction {
uint256 public auctionEndBlock;
uint256 public blockDuration;
constructor(uint256 _blockDuration) {
auctionEndBlock = block.number + _blockDuration;
blockDuration = _blockDuration; // Duration of auction in blocks
}
function finalizeAuction() public {
require(block.number >= auctionEndBlock, "Auction is not yet over.");
// Distribute funds
}
}