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

Auction tokens become stuck when total bid amount is zero

Relevant GitHub Links

https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuctionFactory.sol#L58-L60

https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L134

https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L192-L193

Summary

In the FjordAuction contract, the owner is set as msg.sender in the constructor, but when doing so, the owner becomes the FjordAuctionFactory itself instead of the transaction initiator. Since there is no way to withdraw tokens from the FjordAuctionFactory, when there are no bids and tokens are sent to the owner via auctionEnd, the auction tokens become permanently stuck.

Vulnerability Details

Below is the constructor of the FjordAuction contract;

constructor(
address _fjordPoints,
address _auctionToken,
uint256 _biddingTime,
uint256 _totalTokens
) {
if (_fjordPoints == address(0)) {
revert InvalidFjordPointsAddress();
}
if (_auctionToken == address(0)) {
revert InvalidAuctionTokenAddress();
}
fjordPoints = ERC20Burnable(_fjordPoints);
auctionToken = IERC20(_auctionToken);
owner = msg.sender;
auctionEndTime = block.timestamp.add(_biddingTime);
totalTokens = _totalTokens;
}

The sender of the call is actually the FjordAuctionFactory contract and msg.sender will be FjordAuctionFactory, the Proof-Of-Concept below demonstrates this;

To run this test, a setUp is also required, so you can copy it from this Gist and create a file under the test/unit directory to paste it.

function test_AuctionTokensStuckAtFactoryContract() public {
vm.startPrank(owner);
bytes32 salt = keccak256(abi.encodePacked("salt"));
auctionToken.approve(address(auctionFactory), totalTokens);
vm.recordLogs();
auctionFactory.createAuction(address(auctionToken), biddingTime, totalTokens, salt);
Vm.Log[] memory entries = vm.getRecordedLogs();
address auctionAddress = address(uint160(uint256(entries[2].topics[1])));
emit log_address(auctionAddress);
auction = FjordAuction(auctionAddress);
assertEq(auction.owner() == owner, false);
assertEq(auction.owner(), address(auctionFactory));
skip(biddingTime + 1);
auction.auctionEnd();
assertEq(auctionToken.balanceOf(address(auctionFactory)), totalTokens);
vm.expectRevert();
auctionToken.transferFrom(address(auctionFactory), address(owner), totalTokens);
vm.stopPrank();
}

Impact

If there is no bid or bids are unbid and totalBids is now 0, all auction tokens will be stuck forever.

Tools Used

Foundry

Recommendations

Update the constructor as follows and you can give msg.sender for the owner in the createAuction function (that time the address who starts the tx from the factory will be the owner) or you can take it as a parameter in createAuction and enter it manually.

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.