DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Due to lack of validation, a malicious bidder can receive the **full** (`totalTokens`) amount of the `auctionToken` by spending (bidding) just `1 wei` of his/her BJB token (`fjordPoints`) as a cost

Summary

Due to lack of validation, a malicious bidder can receive the full (totalTokens) amount of the auctionToken by spending (bidding) just 1 wei of his/her BJB token (fjordPoints) as a cost.

Vulnerability Details

When a new FjordAuction contract would be deployed via the FjordAuctionFactory#createAuction(), a given _totalTokens would be set to the totalTokens global variable like this:
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L124
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L136

constructor(
address _fjordPoints,
address _auctionToken,
uint256 _biddingTime,
uint256 _totalTokens ///<--------- @audit
) {
...
fjordPoints = ERC20Burnable(_fjordPoints);
auctionToken = IERC20(_auctionToken);
owner = msg.sender;
auctionEndTime = block.timestamp.add(_biddingTime);
totalTokens = _totalTokens; ///<--------- @audit
}

Once the auction would get started, a bidder would bid via the FjordAuction#bid().
Within the FjordAuction#bid(), a given bidding amount would be added to the totalBids.
Then, the given bidding amount of the BJB token (fjordPoints) would be transferred to the FjordAuction contract (address(this)) like this:
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L143
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L149
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L151

function bid(uint256 amount) external { ///<------- @audit
if (block.timestamp > auctionEndTime) {
revert AuctionAlreadyEnded();
}
bids[msg.sender] = bids[msg.sender].add(amount);
totalBids = totalBids.add(amount); ///<------- @audit
fjordPoints.transferFrom(msg.sender, address(this), amount); ///<------- @audit

Once the current time (block.timestamp) would elapse the auctionEndTime, any user can call the FjordAuction#auctionEnd() to end the auction.
Within the FjordAuction#auctionEnd(), if there was no bids (totalBids == 0), the total amount (totalTokens) of the auctionToken would be transferred back to the FjordAuction contract's owner.
If there were bids (totalBids > 0), the multiplier would be calculated and the pointsToBurn of the BJB token (fjordPoints) would be burned from the FjordAuction contract (address(this)) like this:
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L182-L184
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L192-L195
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L197
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L201

/**
* @notice Ends the auction and calculates claimable tokens for each bidder based on their bid proportion.
*/
function auctionEnd() external {
if (block.timestamp < auctionEndTime) { ///<------- @audit
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true;
emit AuctionEnded(totalBids, totalTokens);
if (totalBids == 0) { ///<--------- @audit - If there is no bids
auctionToken.transfer(owner, totalTokens); ///<--------- @audit
return;
}
multiplier = totalTokens.mul(PRECISION_18).div(totalBids); ///<--------- @audit
// Burn the FjordPoints held by the contract
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn); ///<--------- @audit
}

Finally, bidders can receive the claimable amount of the auctionToken by calling the FjordAuction#claimTokens() like this:
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L217
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L220

function claimTokens() external {
...
uint256 userBids = bids[msg.sender];
...
uint256 claimable = userBids.mul(multiplier).div(PRECISION_18); ///<--------- @audit
bids[msg.sender] = 0;
auctionToken.transfer(msg.sender, claimable); ///<--------- @audit
...

However,

  • within the FjordAuction#bid() above, there is no requirement of the minimum bids amount for a given bidding amount, which is assigned by a bidder (msg.sender).

  • within the FjordAuction#auctionEnd() above, there is no validation to check whether or not the totalBids exceeds the minimum total bids amount that the auction was valid (successful).

If there is no bids (totalBids == 0) until the last minutes and then some user would call the FjordAuction#auctionEnd(), a malicious bidder would monitor the user's TX of the FjordAuction#auctionEnd() and the malicious bidder would **front-run** it with calling the FjordAuction#bid()with1 wei of the BJB token (fjordPoints`).

As a result, the malicious bidder can get the full (totalTokens) amount of the auctionToken by spending (bidding) just 1 wei of his/her BJB token (fjordPoints) as a cost.

Attack scenario

Let's say Alice is a normal user and Bob is a malicious bidder:

  • 1/ A new FjordAuction contract would be deployed and then the auction would get started.

    • At this time, the totalTokens (of the auctionToken) would be set.

  • 2/ For a while since the auction has gotten started, there has been no bids (totalBids == 0).

  • 3/ Eventually, the current time (block.timestamp) would elapse the auctionEndTime (block.timestamp > auctionEndTime)

  • 4/ Alice would see the situation of the step 3/ and then she would call the FjordAuction#auctionEnd().

  • 5/ Bob would monitor the Alice's TX of the step 4/ and he would front-run it with calling the FjordAuction#bid() with 1 wei as a bidding amount.

  • 6/ Bob's TX (step 5/) would be executed first.

  • 7/ Alice's TX (step 4/) would be executed.

  • 8/ Bob would call the FjordAuction#claimTokens() and receive the whole amount (totalTokens) of the auctionToken.

As you can see the scenario above, since the totalTokens (of the auctionToken) was set initially (when the step 1/) and the Bob's TX would be executed first (when the step 6), Bob could receive the whole amount (totalTokens) of the auctionToken by spending just 1 wei of his BJB token (fjordPoints) as a cost.

Impact

The malicious bidder can get the full (totalTokens) amount of the auctionToken by spending (bidding) just 1 wei of his/her BJB token (fjordPoints) as a cost.

Tools Used

  • Foundry

Recommendations

Within the FjordAuction#bids(), consider adding a validation to check whether or not a given bidding amount exceeds the minimum bidding amount.

Or,

Within the FjordAuction#auctionEnd(), consider adding a validation to check whether or not the totalBids exceeds the minimum total bidding amount that the auction was valid (successful).

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.