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

If an auction ends with no bids the auction tokens will be stuck in the factory contract and cannot be retrieved

Summary

When an auction ends without bids, the auction tokens are transferred back to the owner, but the owner of the auction is the factory contract, not the protocol owner address. There is no way to retrieve the tokens from the factory contract so they will be stuck there.

Vulnerability Details

If we take a look at the FjordAuction.sol contract's constructor, we can see that owner is set to msg.sender. Since the factory creates the auction itself using create2, then the owner of an auction will be the Factory contract, and not the address that is invoking the Factory.

If an auction ends without bids then the totalTokens of auctionToken are transferred back to the owner

if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}

They will be transferred back to the Factory contract and not the protocol. There is no way to retrieve the tokens from the Factory contract so they will remain there stuck.

Impact

All auction tokens are stuck in the Factory contract if an auction ends with 0 bids and they remain irretrievable.

Tools Used

Manual review / foundry

PoC

For convenience when writing the test I added a return (address) to the factory's createAuction() function so that I can fetch the newly created auction's address

function createAuction(
...
@>) external onlyOwner returns (address) {
...
emit AuctionCreated(auctionAddress);
@> return auctionAddress;
}
  1. Add import {AuctionFactory} from "../../src/FjordAuctionFactory.sol"; to auction.t.sol

  2. Add AuctionFactory public factory; to the test contract's variables

  3. Add factory = new AuctionFactory(address(fjordPoints)); in the setUp()

PoC:

function testStuck() public {
// initial setup - dealing auction tokens to owner and setting factory owner to "owner" address
deal(address(auctionToken), owner, 1e21);
factory.setOwner(owner);
// owner creates auction, logging balance of both owner and factory to check which one gets the tokens after cancellation
uint256 ownerBalBefore = auctionToken.balanceOf(owner);
uint256 factoryBalBefore = auctionToken.balanceOf(address(factory));
bytes32 salt = keccak256("random");
vm.startPrank(owner);
auctionToken.approve(address(factory), 1e21);
address newAuction = factory.createAuction(address(auctionToken), biddingTime, totalTokens, salt);
// warping time to end auction without bids
vm.warp(block.timestamp + biddingTime);
FjordAuction(newAuction).auctionEnd();
uint256 ownerBalAfter = auctionToken.balanceOf(owner);
uint256 factoryBalAfter = auctionToken.balanceOf(address(factory));
// printing before & after
console.log("Owner balance before:", ownerBalBefore);
console.log("Owner balance after:", ownerBalAfter);
console.log("Factory balance before:", factoryBalBefore);
console.log("Factory balance after:", factoryBalAfter);
}

Result:

Ran 1 test for test/unit/auction.t.sol:TestAuction
[PASS] testStuck() (gas: 961137)
Logs:
Owner balance before: 1000000000000000000000
Owner balance after: 0
Factory balance before: 0
Factory balance after: 1000000000000000000000

Recommendations

Either implement an ERC20 retrieve function in the factory contract or set auction clones' owner to the protocol owner.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year 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.