Summary
The FjordAuction
contains a critical vulnerability which allows malicious actors(miners) to manipulate the block.timestamp
, effectively allowing them to place bids after the official end time of the auction. This potentially leads to unfair advantages and incorrect distributions of tokens.And FjordAuction::auctionEnd
function has to be called manually to end the auction ending
process.
Vulnerability Details
The issue lies in both FjordAuction::bid
and FjordAuction:: auctionEnd
functions
function bid(uint256 amount) external {
if (block.timestamp > auctionEndTime) {
revert AuctionAlreadyEnded();
}
bids[msg.sender] = bids[msg.sender].add(amount);
totalBids = totalBids.add(amount);
fjordPoints.transferFrom(msg.sender, address(this), amount);
emit BidAdded(msg.sender, amount);
}
function auctionEnd() external {
if (block.timestamp < auctionEndTime) {
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true;
emit AuctionEnded(totalBids, totalTokens);
if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}
multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}
Impact
Fairness of the auction process is compromised.
Potential for unfair advantage in the auction.
Risk of incorrect distribution of tokens after the auction.
Loss of confidence in the integrity of the auction system.
Tools Used
Manual Review
Recommendations
Recommendation 1:
use chainlink keepers
to automatically end the auction end
process.
Recommendation 2:
use block.number
instead of block.timestamp
+ uint256 public auctionEndBlock;
+ event AuctionStarted(uint256 durationInBlocks);
+ function startAuction(uint256 durationInBlocks) external onlyOwner{
+ auctionEndBlock = block.number + durationInBlocks;
+ emit AuctionStarted(durationInBlocks);
+ }
- function bid(uint256 amount) external {
+ function bid(uint256 amount) external ReentrancyGuard {
- if (block.timestamp > auctionEndTime) {
+ if (block.number > auctionEndBlock) {
+ auctionEnd();
+ return;
- revert AuctionAlreadyEnded();
}
bids[msg.sender] = bids[msg.sender].add(amount);
totalBids = totalBids.add(amount);
fjordPoints.transferFrom(msg.sender, address(this), amount);
emit BidAdded(msg.sender, amount);
}
- function auctionEnd() external {
+ function auctionEnd() external ReentrancyGuard {
- if (block.timestamp < auctionEndTime) {
+ if (block.number < auctionEndBlock) {
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true;
emit AuctionEnded(totalBids, totalTokens);
if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}
multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}