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

` Auction tokens ` can be stuck in the ` FjordAuctionFactory ` contract

Description + Impact

The owner of FjordAuctionFactory contract will execute FjordAuctionFactory :: createAuction() to deploy a copy of FjordAuction and to transfer a specific amount of auction tokens to the newly deployed FjordAuction through this function .

In FjordAuction , the owner state variable will be equal with the value of msg.sender which is the FjordAuctionFactory contract .

In FjordAuction :: auctionEnd() , if totalBids state variable is equal with 0 , all the auction tokens will be transfered to the owner of the FjordAuction contract which is the FjordAuctionFactory .

The problem is that the auction tokens can't be retrieved no more from FjordAuctionFactory because the contract only has 2 functions and no function deals with the retrieve of tokens .

The issue can be prevented if the owner of the FjordAuction will be set in the constructor to the address that executes the createAuction() function

( ! ) This means that this issue is only possible if there were no user to make a bid, it is a very specific scenario that will most likely will not happen, I am aware of this !

PoC

How to execute the PoC ?

  • Create a Foundry project with everything needed

  • Add the PoC in test/PoC.t.sol

  • Execute the PoC using the command forge test --match-path test/PoC.t.sol --match-test test________ -vv

PoC
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0 ;
import { Test } from "lib/forge-std/src/Test.sol" ;
import { console } from "lib/forge-std/src/console.sol" ;
import { FjordAuction } from 'src/FjordAuction.sol' ;
import { AuctionFactory } from 'src/FjordAuctionFactory.sol' ;
import { FjordPoints } from 'src/FjordPoints.sol' ;
import { FjordStaking } from 'src/FjordStaking.sol' ;
import { FjordToken } from 'src/FjordToken.sol' ;
import { ERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { IFjordPoints } from "src/interfaces/IFjordPoints.sol";
import { IERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import { ERC20Burnable } from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import { ISablierV2Lockup } from "lib/v2-core/src/interfaces/ISablierV2Lockup.sol";
import { ISablierV2LockupLinear } from "lib/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
import { Broker , LockupLinear } from "lib/v2-core/src/types/DataTypes.sol";
import { ud60x18 } from "@prb/math/src/UD60x18.sol";
interface I______ERC20__decimals {
function decimals() external view returns (uint8);
}
contract Mock______ERC20 is ERC20 {
uint8 private _decimals;
constructor( string memory a , string memory b , uint8 c ) ERC20( a , b ) {
_decimals = c;
}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
}
contract Contract is Test {
struct Users {
User PRIVILEGED ;
User ATTACKER ;
User ______USER__1______ ;
User ______USER__2______ ;
User ______USER__3______ ;
User ______USER__4______ ;
}
struct User {
uint256 PRIVATE__KEY ;
address ADDRESS ;
}
struct Scope {
FjordAuction ______FjordAuction______ ;
AuctionFactory ______AuctionFactory______ ;
FjordPoints ______FjordPoints______ ;
FjordStaking ______FjordStaking______ ;
FjordToken ______FjordToken______ ;
}
struct Mocks {
Mock______ERC20 ______Mock______ERC20______MOCK__ERC20__1 ;
}
struct Addresses {
Ethereum Ethereum ;
}
struct Ethereum {
ISablierV2LockupLinear ______Sablier______ ;
}
Users ________Users ;
Scope ________Scope ;
Mocks ________Mocks ;
Addresses ________Addresses ;
function test________() public {
fork( 0 ) ;
users() ;
// NOTE :: Initialize miscellaneous state variables used across the PoC
{
________Addresses.Ethereum.______Sablier______ = ISablierV2LockupLinear( 0xB10daee1FCF62243aE27776D7a92D39dC8740f95 ) ;
}
// NOTE :: Deploy the smart contracts and mocks
{
/// Deploy mock ` MOCK__ERC20__1 ` ///
{
vm.startPrank( ________Users.PRIVILEGED.ADDRESS );
________Mocks.______Mock______ERC20______MOCK__ERC20__1 = new Mock______ERC20(
" MOCK__ERC20__1 " ,
" MOCK__ERC20__1 " ,
18
);
vm.stopPrank();
}
/// Deploy ` FjordToken ` ///
{
vm.startPrank( ________Users.PRIVILEGED.ADDRESS );
________Scope.______FjordToken______ = new FjordToken( );
vm.stopPrank();
}
/// Deploy ` FjordPoints ` ///
{
vm.startPrank( ________Users.PRIVILEGED.ADDRESS );
________Scope.______FjordPoints______ = new FjordPoints( );
vm.stopPrank();
}
/// Deploy ` AuctionFactory ` ///
{
vm.startPrank( ________Users.PRIVILEGED.ADDRESS );
________Scope.______AuctionFactory______ = new AuctionFactory({
_fjordPoints : address( ________Scope.______FjordPoints______ )
});
vm.stopPrank();
}
/// Deploy ` FjordStaking ` ///
{
vm.startPrank( ________Users.PRIVILEGED.ADDRESS );
________Scope.______FjordStaking______ = new FjordStaking({
_fjordToken : address( ________Scope.______FjordToken______ ) ,
_rewardAdmin : address( ________Users.PRIVILEGED.ADDRESS ) ,
_sablier : address( ________Addresses.Ethereum.______Sablier______ ) ,
_authorizedSablierSender : address( ________Users.PRIVILEGED.ADDRESS ) ,
_fjordPoints : address( ________Scope.______FjordPoints______ )
});
vm.stopPrank();
}
}
// NOTE :: Simulate the deployment of ` FjordAuction ` because ` AuctionFactory :: createAuction() ` doesn't return the deployed address to make testing easier
/// Deploy ` FjordAuction ` ///
{
vm.startPrank( address( ________Scope.______AuctionFactory______ ) );
________Scope.______FjordAuction______ = new FjordAuction({
_fjordPoints : address( ________Scope.______FjordPoints______ ) ,
_auctionToken : address( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ) ,
_biddingTime : uint256( 12 hours ) ,
_totalTokens : uint256(
( 1000 ) * ( 10 ** I______ERC20__decimals( address( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ) ).decimals() )
)
});
vm.stopPrank();
}
// NOTE :: Give money
/// ERC20 :: ` MOCK__ERC20__1 ` :: ` << FjordAuction >> ` ///
{
( address[] memory ADDRESS , ) = get_list_of_addresses() ;
address ERC20 = address( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ) ;
uint256 AMOUNT__OF__ERC20 = ( 1000 ) * ( 10 ** I______ERC20__decimals( ERC20 ).decimals() ) ;
for ( uint256 III = 6 ; III < 7 ; III++ ) {
deal(
address( ERC20 ) ,
address( ADDRESS[ III ] ) ,
AMOUNT__OF__ERC20
);
}
}
{
require(
(
________Scope.______FjordAuction______.owner()
)
==
(
address( ________Scope.______AuctionFactory______ )
) ,
unicode' ⛔️ '
);
}
{
require(
(
IERC20( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ).balanceOf( address( ________Scope.______AuctionFactory______ ) )
)
==
(
( 0 ) * ( 10 ** I______ERC20__decimals( address( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ) ).decimals() )
) ,
unicode' ⛔️ '
);
}
skip( 12 hours + 5 minutes ) ;
/// ` FjordAuction ` :: auctionEnd ///
{
vm.startPrank( ________Users.ATTACKER.ADDRESS );
________Scope.______FjordAuction______.auctionEnd( );
vm.stopPrank();
}
{
require(
(
IERC20( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ).balanceOf( address( ________Scope.______AuctionFactory______ ) )
)
==
(
( 1000 ) * ( 10 ** I______ERC20__decimals( address( ________Mocks.______Mock______ERC20______MOCK__ERC20__1 ) ).decimals() )
) ,
unicode' ⛔️ '
);
}
}
function users() private {
( ________Users.PRIVILEGED.ADDRESS , ________Users.PRIVILEGED.PRIVATE__KEY ) = makeAddrAndKey( ' PRIVILEGED ' ) ;
( ________Users.ATTACKER.ADDRESS , ________Users.ATTACKER.PRIVATE__KEY ) = makeAddrAndKey( ' ATTACKER ' ) ;
( ________Users.______USER__1______.ADDRESS , ________Users.______USER__1______.PRIVATE__KEY ) = makeAddrAndKey( ' USER 1 ' ) ;
( ________Users.______USER__2______.ADDRESS , ________Users.______USER__2______.PRIVATE__KEY ) = makeAddrAndKey( ' USER 2 ' ) ;
( ________Users.______USER__3______.ADDRESS , ________Users.______USER__3______.PRIVATE__KEY ) = makeAddrAndKey( ' USER 3 ' ) ;
( ________Users.______USER__4______.ADDRESS , ________Users.______USER__4______.PRIVATE__KEY ) = makeAddrAndKey( ' USER 4 ' ) ;
}
function fork(
uint8 index
) private {
string[] memory CHAIN = new string[]( 1 ) ;
CHAIN[ 0 ] = "https://rpc.ankr.com/eth" ; // Ethereum
vm.createSelectFork( CHAIN[ index ] ) ;
}
function get_list_of_addresses() private
returns( address[] memory ADDRESS , string[] memory NAME__OF__ADDRESS )
{
ADDRESS = new address[]( 11 ) ;
ADDRESS[ 0 ] = address( ________Users.ATTACKER.ADDRESS ) ;
ADDRESS[ 1 ] = address( ________Users.______USER__1______.ADDRESS ) ;
ADDRESS[ 2 ] = address( ________Users.______USER__2______.ADDRESS ) ;
ADDRESS[ 3 ] = address( ________Users.______USER__3______.ADDRESS ) ;
ADDRESS[ 4 ] = address( ________Users.______USER__4______.ADDRESS ) ;
ADDRESS[ 5 ] = address( ________Users.PRIVILEGED.ADDRESS ) ;
ADDRESS[ 6 ] = address( ________Scope.______FjordAuction______ ) ;
ADDRESS[ 7 ] = address( ________Scope.______AuctionFactory______ ) ;
ADDRESS[ 8 ] = address( ________Scope.______FjordPoints______ ) ;
ADDRESS[ 9 ] = address( ________Scope.______FjordStaking______ ) ;
ADDRESS[ 10 ] = address( ________Scope.______FjordToken______ ) ;
NAME__OF__ADDRESS = new string[]( 11 ) ;
NAME__OF__ADDRESS[ 0 ] = " ATTACKER " ;
NAME__OF__ADDRESS[ 1 ] = " USER 1 " ;
NAME__OF__ADDRESS[ 2 ] = " USER 2 " ;
NAME__OF__ADDRESS[ 3 ] = " USER 3 " ;
NAME__OF__ADDRESS[ 4 ] = " USER 4 " ;
NAME__OF__ADDRESS[ 5 ] = " PRIVILEGED " ;
NAME__OF__ADDRESS[ 6 ] = " << FjordAuction >> " ;
NAME__OF__ADDRESS[ 7 ] = " << AuctionFactory >> " ;
NAME__OF__ADDRESS[ 8 ] = " << FjordPoints >> " ;
NAME__OF__ADDRESS[ 9 ] = " << FjordStaking >> " ;
NAME__OF__ADDRESS[ 10 ] = " << FjordToken >> " ;
}
}
The output from the terminal
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 615.90ms (615.29ms CPU time)
Ran 1 test suite in 617.29ms (615.90ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Updates

Lead Judging Commences

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