| 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 |
|
2022-03-09 |
2620000 |
0 |
|
|
|
|
|
forge test --match-contract Exploit_FantasmFinance -vvv |
|
|
- Call
mintwithout providing any backing for your mint - Profit
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);
}
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"
- Bad Guys NFT - Missing validation on mint allows claiming unlimited tokens
- Compound TUSD Integration - Faulty token integration in protocol functions
- Uranium - Business logic error in core protocol invariant