Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

README.md

title description type network date loss_usd returned_usd tags subcategory vulnerable_contracts tokens_lost attacker_addresses malicious_token attack_block reproduction_command attack_txs sources
Fantasm Finance
Minting tokens without required backing collateral
Exploit
fantom
2022-03-09
2620000
0
business logic
data validation
0x880672ab1d46d987e5d663fc7476cd8df3c9f937
FTM
0x47091e015b294b935babda2d28ad44e3ab07ae8d
0x944b58c9b3b49487005cead0ac5d71c857749e3e
32968740
forge test --match-contract Exploit_FantasmFinance -vvv
0x0c850bd8b8a8f4eb3f3a0298201499f794e0bfa772f620d862b13f0a44eadb82

Step-by-step

  1. Call mint without providing any backing for your mint
  2. Profit

Detailed Description

As most tokens, you can mint Fantasm on some conditions. Particularly, Fantasm wanted to ask for some native tokens _ftmIn and some amount of an extra token _fantasmIn to mint some XFTM.

So, in short, you need to give FTM (native token) and FXM (non-native, is burned) to mint some XFTM.

The problem is that the mint function never checks for the amount of FMT deposited, allowing the attacker to mint with only FXM.

    function mint(uint256 _fantasmIn, uint256 _minXftmOut) external payable nonReentrant {
        require(!mintPaused, "Pool::mint: Minting is paused");
        uint256 _ftmIn = msg.value;
        address _minter = msg.sender;

        // This is  supposed to mint. There are three parameters:
        // 1. Native token passed `_ftmIn`
        // 2. _fantasmIn an amount
        // 3.`_minXftmOut` slippage protection
        // What you say here is "Giving you _ftmIn native, I want at least minXftmOut, and I will put _fantasmIn as collateral"

        (uint256 _xftmOut, , uint256 _minFantasmIn, uint256 _ftmFee) = calcMint(_ftmIn, _fantasmIn);
        require(_minXftmOut <= _xftmOut, "Pool::mint: slippage");
        require(_minFantasmIn <= _fantasmIn, "Pool::mint: Not enough Fantasm input");
        require(maxXftmSupply >= xftm.totalSupply() + _xftmOut, "Pool::mint: > Xftm supply limit");

        WethUtils.wrap(_ftmIn);
        userInfo[_minter].lastAction = block.number;

        if (_xftmOut > 0) {
            userInfo[_minter].xftmBalance = userInfo[_minter].xftmBalance + _xftmOut;
            unclaimedXftm = unclaimedXftm + _xftmOut;
        }

        if (_minFantasmIn > 0) {
            fantasm.safeTransferFrom(_minter, address(this), _minFantasmIn);
            fantasm.burn(_minFantasmIn);
        }

        if (_ftmFee > 0) {
            WethUtils.transfer(feeReserve, _ftmFee);
        }

        emit Mint(_minter, _xftmOut, _ftmIn, _fantasmIn, _ftmFee);
    }

Possible mitigations

The obvious recommendation here is "check that counterpart token is received", but this can be covered by a test. Make sure to have negative testing as part of the suite of your contract, with tests that check that "should not mint XFTM without backing native token"

Related