In the FjordAuction
contract, tokens are auctioned by points, reward for auctioned tokens are calculated based on total bid points. The time check in handling bidding, unbidding and auction ending is faulty, which makes a malicious user to bid even after auctionEnd
has been called, and get more auction tokens than deserved.
We see that in bid
function, users cannot bid anymore when the timestamp exceeds auctionEndTime
:
And similar logic is implemented here in auctionEnd
:
However, when block.timestamp == auctionEndTime
, both function can be called, regardless of calling order. Also, we notice that, during reward distribution, multiplier
variable is used to determine the final amount:
So consider in a scenario, where we have block.timestamp == auctionEndTime
, and at this moment, the total bids is 20, and total tokens is at 1000, for demonstration purpose. Now, Alice comes in, calls auctionEnd
, and sets multiplier
to 1000 / 20 * 1e18, making it 50 * 1e18. Then in the very next trnsaction, Alice bids another 20 points, the bid would go through, as the function does not check if the auction has ended, this will not change the multiplier, but will increase Alice's bidding balance. Next, Alice calls claimTokens
, based on the calculation, she would get 20 * 50 * 1e18 / 1e18 = 1000 auction tokens, which is the entire token supply, leaving other users to unable to claim their deserved tokens.
All auction tokens can potentially be claimed, and also leaving other legit users to lose their rewards. Average block time is about 12 seconds, the change of this happening is quite high, combined with loss/steal of funds, this should be high severity.
Manual review
Change the timestamp comparison, also add additional check on auction end status.
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.