I have created POC for demonstrating the vulnerability regarding auction ends before the end time. I have created test script FjordAuctionTest.t.sol
and added function testCannotEndAuctionBeforeEndTime()
.
pragma solidity =0.8.21;
import "lib/forge-std/src/Test.sol";
import { FjordAuction } from "../src/FjordAuction.sol";
import { ERC20Mock } from "lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol";
import { ERC20BurnableMock } from "./mocks/ERC20BurnableMock.sol";
contract FjordAuctionTest is Test {
ERC20Mock public auctionToken;
ERC20BurnableMock public fjordPoints;
FjordAuction public auction;
function setUp() public {
auctionToken = new ERC20Mock();
fjordPoints = new ERC20BurnableMock("FjordPoints", "FP");
auction = new FjordAuction(address(fjordPoints), address(auctionToken), 2 days, 1000 ether);
fjordPoints.mint(address(this), 1000 ether);
fjordPoints.approve(address(auction), 1000 ether);
auctionToken.mint(address(this), 1000 ether);
auctionToken.transfer(address(auction), 1000 ether);
}
function testCannotDeployWithInvalidFjordPointsAddress() public {
vm.expectRevert(FjordAuction.InvalidFjordPointsAddress.selector);
new FjordAuction(address(0), address(auctionToken), 2 days, 10 ether);
}
function testCannotDeployWithInvalidAuctionTokenAddress() public {
vm.expectRevert(FjordAuction.InvalidAuctionTokenAddress.selector);
new FjordAuction(address(fjordPoints), address(0), 1 days, 10 ether);
}
function testCannotBidAfterAuctionEnded() public {
vm.warp(block.timestamp + 2 days);
vm.expectRevert(FjordAuction.AuctionAlreadyEnded.selector);
auction.bid(100 ether);
}
function testCannotUnbidMoreThanBidAmount() public {
fjordPoints.approve(address(auction), 100 ether);
auction.bid(100 ether);
vm.expectRevert(FjordAuction.InvalidUnbidAmount.selector);
auction.unbid(200 ether);
}
function testCannotUnbidWithoutBids() public {
vm.expectRevert(FjordAuction.NoBidsToWithdraw.selector);
auction.unbid(100 ether);
}
function testCannotClaimTokensBeforeAuctionEnd() public {
vm.expectRevert(FjordAuction.AuctionNotYetEnded.selector);
auction.claimTokens();
}
function testCannotClaimTokensWithNoBids() public {
vm.warp(block.timestamp + 1 days);
auction.auctionEnd();
vm.expectRevert(FjordAuction.NoTokensToClaim.selector);
auction.claimTokens();
}
function testCannotEndAuctionBeforeEndTime() public {
vm.expectRevert(FjordAuction.AuctionNotYetEnded.selector);
auction.auctionEnd();
}
function testCannotEndAuctionTwice() public {
vm.warp(block.timestamp + 1 days);
auction.auctionEnd();
vm.expectRevert(FjordAuction.AuctionEndAlreadyCalled.selector);
auction.auctionEnd();
}
function testClaimTokensWithNoAuctionTokens() public {
FjordAuction auctionWithNoTokens = new FjordAuction(
address(fjordPoints),
address(auctionToken),
2 days,
0
);
fjordPoints.mint(address(this), 100 ether);
fjordPoints.approve(address(auctionWithNoTokens), 100 ether);
auctionWithNoTokens.bid(100 ether);
vm.warp(block.timestamp + 2 days);
auctionWithNoTokens.auctionEnd();
uint256 initialBalance = auctionToken.balanceOf(address(this));
try auctionWithNoTokens.claimTokens() {
assertEq(auctionToken.balanceOf(address(this)), initialBalance);
} catch (bytes memory reason) {
assert(false);
}
}
function testBidAtExactAuctionEndTime() public {
FjordAuction shortAuction = new FjordAuction(
address(fjordPoints),
address(auctionToken),
1 minutes,
100 ether
);
vm.warp(block.timestamp + 1 minutes);
fjordPoints.approve(address(shortAuction), 100 ether);
shortAuction.bid(100 ether);
assertEq(shortAuction.bids(address(this)), 100 ether);
}
function testSmallestPossibleBid() public {
fjordPoints.approve(address(auction), 1);
auction.bid(1);
assertEq(auction.bids(address(this)), 1);
}
function testAuctionEndWithZeroBids() public {
assertEq(auction.totalBids(), 0);
vm.warp(block.timestamp + 2 days);
auction.auctionEnd();
assertEq(auctionToken.balanceOf(address(this)), 1000 ether);
}
function testBidAtExactAuctionEndTime_Success() public {
FjordAuction shortAuction = new FjordAuction(
address(fjordPoints),
address(auctionToken),
1 minutes,
100 ether
);
vm.warp(block.timestamp + 1 minutes);
fjordPoints.approve(address(shortAuction), 100 ether);
shortAuction.bid(100 ether);
assertEq(shortAuction.bids(address(this)), 100 ether);
}
function testBidAtAfterAuctionEndTime_Fail() public {
FjordAuction shortAuction = new FjordAuction(
address(fjordPoints),
address(auctionToken),
1 minutes,
100 ether
);
vm.warp(block.timestamp + 1 minutes + 1 seconds);
fjordPoints.approve(address(shortAuction), 100 ether);
vm.expectRevert(FjordAuction.AuctionAlreadyEnded.selector);
shortAuction.bid(100 ether);
}
}