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

Permanent loss of tokens if FjordAuction.sol:totalBids is 0 in Auction

Summary

If the total bids in the auction for a token are zero, the tokens are transferred to the AuctionFactory. However, since the FjordAuctionFactory.sol:AuctionFactory lacks any methods to transfer those funds, the tokens become permanently locked there.

Vulnerability Details

In FjordAuction.sol:auctionEnd at L192-L195, if FjordAuction.sol:totalBids is zero, the tokens are transferred to the owner, which is FjordAuctionFactory.sol:AuctionFactory. Since the FjordAuctionFactory.sol:AuctionFactory contract lacks any methods to utilize or transfer those tokens, they become permanently stuck in the contract.

Impact

Impact: High

Likelihood: Medium

The tokens became stuck, making them unusable by anyone.

Proof-of-concept

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity =0.8.21;
import {Test} from "forge-std/Test.sol";
import {FjordAuction} from "../../src/FjordAuction.sol";
import {FjordPoints} from "../../src/FjordPoints.sol";
import {FjordToken} from "../../src/FjordToken.sol";
import {AuctionFactory} from "../../src/FjordAuctionFactory.sol";
contract AuditTest is Test {
AuctionFactory public factory;
FjordPoints public points;
function setUp() public {
FjordPoints pointsContract = new FjordPoints();
factory = new AuctionFactory(address(pointsContract));
}
function test_tokenStuckInFactory() public {
FjordToken sampleAuctionToken = new FjordToken();
//approving factory contract to use tokens
sampleAuctionToken.approve(address(factory), 1000 ether);
//creating a new auction contract
address auctionAddress = factory.createAuction(
address(sampleAuctionToken),
block.timestamp + 1000,
1000 ether,
bytes32(uint(123))
);
vm.warp(block.timestamp + 1001);
FjordAuction auction = FjordAuction(auctionAddress);
//auction ended
auction.auctionEnd();
// balance of factory contract will be 1000 tokens which got stuck there forever
require(sampleAuctionToken.balanceOf(address(factory)) == 1000 ether);
}
}

Slight change for PoC

  • AuctionFactory.sol:createAuction returns the newly created auction contract address

/**
* @notice Creates a new auction contract using create2.
* @param biddingTime The duration of the auction in seconds.
* @param totalTokens The total number of tokens to be auctioned.
* @param salt A unique salt for create2 to generate a deterministic address.
*/
function createAuction(
address auctionToken,
uint256 biddingTime,
uint256 totalTokens,
bytes32 salt
) external onlyOwner
+ returns (address)
{
address auctionAddress = address(
new FjordAuction{salt: salt}(
fjordPoints,
auctionToken,
biddingTime,
totalTokens
)
);
// Transfer the auction tokens from the msg.sender to the new auction contract
IERC20(auctionToken).transferFrom(
msg.sender,
auctionAddress,
totalTokens
);
emit AuctionCreated(auctionAddress);
+ return auctionAddress;
}

Recommendations

Pass the owner's address as a parameter to the FjordAuction.sol constructor instead of using msg.sender. This new owner should be an EOA (Externally Owned Account).

/**
* @dev Sets the token contract address and auction duration.
* @param _fjordPoints The address of the FjordPoints token contract.
* @param _biddingTime The duration of the auction in seconds.
* @param _totalTokens The total number of tokens to be auctioned.
*/
constructor(
address _fjordPoints,
address _auctionToken,
uint256 _biddingTime,
uint256 _totalTokens,
+ address _owner
) {
if (_fjordPoints == address(0)) {
revert InvalidFjordPointsAddress();
}
if (_auctionToken == address(0)) {
revert InvalidAuctionTokenAddress();
}
fjordPoints = ERC20Burnable(_fjordPoints);
auctionToken = IERC20(_auctionToken);
- owner = msg.sender;
+ owner = _owner;
auctionEndTime = block.timestamp.add(_biddingTime);
totalTokens = _totalTokens;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

If no bids are placed during the auction, the `auctionToken` will be permanently locked within the `AuctionFactory`

An auction with 0 bids will get the `totalTokens` stuck inside the contract. Impact: High - Tokens are forever lost Likelihood - Low - Super small chances of happening, but not impossible

Support

FAQs

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