The condition to check if a user can bid or unbid intersects with the condition to end an auction, allowing these actions to be executed concurrently when the current timestamp matches the auction end time.
In the FjordAuction contract, users are allowed to bid or unbid until the auctionEndTime timestamp:
Note here that the limit is inclusive , i.e. the action is allowed if block.timestamp == auctionEndTime.
Similarly, the function to end an auction checks the auctionEndTime timestamp:
Note here that the limit is inclusive too, and the function can also be called when block.timestamp == auctionEndTime.
This intersection in the conditions of the validations allow the bid() and unbid() functions to be called after the auctionEnd() function has been executed.
Unbidding is likely to fail if bundled after the call to end an auction since ending an auction burns all of the fjord points, causing the transfer in bid to fail.
However, bidding would certainly lead to issues as a user can bid after the multiplier has been fixed, allowing them to enter the auction without affecting the total amount of bids.
If executed maliciously, this can be used to bid any amount of points without affecting the multiplier, and will ultimately cause accounting issues as not all auctioned tokens will be enough to cover all of the bidders positions. Eventually, one or more honest bidders will be left with nothing to claim.
As a quick example to show the issue, let's say there are 100 tokens to auction and users bidded 100 fjord points. A malicious user could submit the following batch of actions at the auction end timestamp:
Call auctionEnd() to end the auction and fix the multiplier, which will be 1 (1e18) since total tokens equals total bids.
Call bid() with 100 fjord points (the same as the current total bids). Here totalBids is increased but since the multiplier has been fixed, the modification has no effect.
Call claimTokens() to withdraw the auction tokens. The position of the attacker will be calculated as 100 tokens (100 * 1e18 / 1e18), effectively withdrawing the total of the auctioned tokens. All honest bidders will be left with nothing to claim.
Important to note that step 2 doesn't really require to send an equal amount of points as the total bidded, the attacker can take advantage of the fixed multiplier with any given amount of points, and would also result in a loss for a honest bidder as some portion of the auctioned tokens will be redirected to the attacker.
None.
Change the condition in the auctionEnd() function so that the check also discards the exact auctionEndTime moment, avoiding the clashing with bid() or unbid().
The protocol doesn't properly treat the `block.timestamp == auctionEndTime` case. Impact: High - There are at least two possible impacts here: 1. By chance, user bids could land in a block after the `auctionEnd()` is called, not including them in the multiplier calculation, leading to a situation where there are insufficient funds to pay everyone's claim; 2. By malice, where someone can use a script to call `auctionEnd()` + `bid(totalBids)` + `claimTokens()`, effectively depriving all good faith bidders from tokens. Likelihood: Low – The chances of getting a `block.timestamp == auctionEndTime` are pretty slim, but it’s definitely possible.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.