msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: JUSD borrow fee rate is less than it should be
Severity: medium

Description:
The borrow fee rate calculation is wrong causing the protocol to take less fees than it should.\\nThe borrowFeeRate is calculated through getTRate():\\n```\\n    function getTRate() public view returns (uint256) {\\n        uint256 timeDifference = block.timestamp - uint256(lastUpdateTimestamp);\\n        return\\n            t0Rate +\\n            (borrowFeeRate * timeDifference) /\\n            JOJOConstant.SECONDS_PER_YEAR;\\n    }\\n```\\n\\n`t0Rate` is initialized as `1e18` in the test contracts:\\n```\\n    constructor(\\n        uint256 _maxReservesNum,\\n        address _insurance,\\n        address _JUSD,\\n        address _JOJODealer,\\n        uint256 _maxPerAccountBorrowAmount,\\n        uint256 _maxTotalBorrowAmount,\\n        uint256 _borrowFeeRate,\\n        address _primaryAsset\\n    ) {\\n        // // rest of code\\n        t0Rate = JOJOConstant.ONE;\\n    }\\n```\\n\\n`SECONDS_PER_YEAR` is equal to `365 days` which is 60 * 60 * 24 * 365 = 31536000:\\n```\\nlibrary JOJOConstant {\\n    uint256 public constant SECONDS_PER_YEAR = 365 days;\\n}\\n```\\n\\nAs time passes, `getTRate()` value will increase. When a user borrows JUSD the contract doesn't save the actual amount of JUSD they borrow, `tAmount`. Instead, it saves the current "value" of it, t0Amount:\\n```\\n    function _borrow(\\n        DataTypes.UserInfo storage user,\\n        bool isDepositToJOJO,\\n        address to,\\n        uint256 tAmount,\\n        address from\\n    ) internal {\\n        uint256 tRate = getTRate();\\n        //        tAmount % tRate ？ tAmount / tRate + 1 ： tAmount % tRate\\n        uint256 t0Amount = tAmount.decimalRemainder(tRate)\\n            ? tAmount.decimalDiv(tRate)\\n            : tAmount.decimalDiv(tRate) + 1;\\n        user.t0BorrowBalance += t0Amount;\\n```\\n\\nWhen you repay the JUSD, the same calculation is done again to decrease the borrowed amount. Meaning, as time passes, you have to repay more JUSD.\\nLet's say that JUSDBank was live for a year with a borrowing fee rate of 10% (1e17). `getTRate()` would then return: $1e18 + 1e17 * 31536000 / 31536000 = 1.1e18$\\nIf the user now borrows 1 JUSD we get: $1e6 * 1e18 / 1.1e18 ~= 909091$ for `t0Amount`. That's not the expected 10% decrease. Instead, it's about 9.1%.

Impact:
Users are able to borrow JUSD for cheaper than expected

Recommended mitigation:
Change formula to: `t0Amount = tAmount - tAmount.decimalMul(tRate)` where `t0Rate` is initialized with `0` instead of `1e18`.
Vulnerability: Subaccount#execute lacks payable
Severity: medium

Description:
`Subaccount#execute` lacks `payable`. If `value` in `Subaccount#execute` is not zero, it could always revert.\\n`Subaccount#execute` lacks `payable`. The caller cannot send the value.\\n```\\nfunction execute(address to, bytes calldata data, uint256 value) external onlyOwner returns (bytes memory){\\n        require(to != address(0));\\n->      (bool success, bytes memory returnData) = to.call{value: value}(data);\\n        if (!success) {\\n            assembly {\\n                let ptr := mload(0x40)\\n                let size := returndatasize()\\n                returndatacopy(ptr, 0, size)\\n                revert(ptr, size)\\n            }\\n        }\\n        emit ExecuteTransaction(owner, address(this), to, data, value);\\n        return returnData;\\n    }\\n```\\n\\nThe `Subaccount` contract does not implement receive() `payable` or fallback() `payable`, so it is unable to receive value (eth) . Therefore, `Subaccount#execute` needs to add `payable`.

Impact:
`Subaccount#execute` cannot work if `value` != 0.

Recommended mitigation:
Add a receive() external `payable` to the contract or `execute()` to add a `payable` modifier.
Vulnerability: It's possible to reset primaryCredit and secondaryCredit for insurance account
Severity: medium

Description:
When because of negative credit after liquidations of another accounts, insurance address doesn't pass `isSafe` check, then malicious user can call JOJOExternal.handleBadDebt and reset both primaryCredit and secondaryCredit for insurance account.\\n`insurance` account is handled by JOJO team. Team is responsible to top up this account in order to cover losses. When bad debt is handled, then its negative credit value is added to the `insurance` account. Because of that it's possible that primaryCredit of `insurance` account is negative and `Liquidation._isSafe(state, insurance) == false`.\\n```\\n    function handleBadDebt(Types.State storage state, address liquidatedTrader)\\n        external\\n    {\\n        if (\\n            state.openPositions[liquidatedTrader].length == 0 &&\\n            !Liquidation._isSafe(state, liquidatedTrader)\\n        ) {\\n            int256 primaryCredit = state.primaryCredit[liquidatedTrader];\\n            uint256 secondaryCredit = state.secondaryCredit[liquidatedTrader];\\n            state.primaryCredit[state.insurance] += primaryCredit;\\n            state.secondaryCredit[state.insurance] += secondaryCredit;\\n            state.primaryCredit[liquidatedTrader] = 0;\\n            state.secondaryCredit[liquidatedTrader] = 0;\\n            emit HandleBadDebt(\\n                liquidatedTrader,\\n                primaryCredit,\\n                secondaryCredit\\n            );\\n        }\\n    }\\n```\\n\\nSo it's possible for anyone to call `handleBadDebt` for `insurance` address, once its primaryCredit is negative and `Liquidation._isSafe(state, insurance) == false`. This will reset both primaryCredit and secondaryCredit variables to 0 and break `insurance` calculations.

Impact:
Insurance primaryCredit and secondaryCredit variables are reset.

Recommended mitigation:
Do not allow `handleBadDebt` call with insurance address.
Vulnerability: Lack of burn mechanism for JUSD repayments causes oversupply of JUSD
Severity: medium

Description:
`JUSDBank.repay()` allow users to repay their JUSD debt and interest by transfering in JUSD tokens. Without a burn mechanism, it will cause an oversupply of JUSD that is no longer backed by any collateral.\\n`JUSDBank` receives JUSD tokens for the repayment of debt and interest. However, there are no means to burn these tokens, causing JUSD balance in `JUSDBank` to keep increasing.\\nThat will lead to an oversupply of JUSD that is not backed by any collateral. And the oversupply of JUSD will increase significantly during market due to mass repayments from liquidation.\\n```\\n    function _repay(\\n        DataTypes.UserInfo storage user,\\n        address payer,\\n        address to,\\n        uint256 amount,\\n        uint256 tRate\\n    ) internal returns (uint256) {\\n        require(amount != 0, JUSDErrors.REPAY_AMOUNT_IS_ZERO);\\n        uint256 JUSDBorrowed = user.t0BorrowBalance.decimalMul(tRate);\\n        uint256 tBorrowAmount;\\n        uint256 t0Amount;\\n        if (JUSDBorrowed <= amount) {\\n            tBorrowAmount = JUSDBorrowed;\\n            t0Amount = user.t0BorrowBalance;\\n        } else {\\n            tBorrowAmount = amount;\\n            t0Amount = amount.decimalDiv(tRate);\\n        }\\n        IERC20(JUSD).safeTransferFrom(payer, address(this), tBorrowAmount);\\n        user.t0BorrowBalance -= t0Amount;\\n        t0TotalBorrowAmount -= t0Amount;\\n        emit Repay(payer, to, tBorrowAmount);\\n        return tBorrowAmount;\\n    }\\n```\\n

Impact:
To maintain its stability, JUSD must always be backed by more than 1 USD worth of collateral.\\nWhen there is oversupply of JUSD that is not backed by any collateral, it affects JUSD stability and possibly lead to a depeg event.

Recommended mitigation:
Instead of transfering to the JUSDBank upon repayment, consider adding a burn mechanism to reduce the supply of JUSD so that it will be adjusted automatically.
Vulnerability: UniswapPriceAdaptor fails after updating impact
Severity: medium

Description:
The `impact` variable can have a maximum value of `uint32` (=4.294.967.295) after updating. This is too low and will cause the `UniswapPriceAdaptor#getMarkPrice()` function to revert.\\nWhen initialized, the `impact` variable is a `uint256`. However, in the `updateImpact` function, the newImpact is a `uint32`.\\n```\\n    function updateImpact(uint32 newImpact) external onlyOwner {\\n        emit UpdateImpact(impact, newImpact);\\n        impact = newImpact;\\n    }\\n```\\n\\nThe new `impact` variable will be too small because in the getMarkPrice() function, we need diff * 1e18 / JOJOPriceFeed <= impact:\\n```\\n    require(diff * 1e18 / JOJOPriceFeed <= impact, "deviation is too big");\\n```\\n\\nThe result of `diff * 1e18 / JOJOPriceFeed <= impact` is a number with e18 power. It is very likely that it is larger than the `impact` variable which is a `uint32`. The function getMarkPrice() will revert.

Impact:
The UniswapPriceAdaptor will malfunction and not return the price from Uniswap Oracle.

Recommended mitigation:
Change the newImpact argument from uint32 to uint256.\\n```\\n// Remove the line below\\n    function updateImpact(uint32 newImpact) external onlyOwner {\\n// Add the line below\\n    function updateImpact(uint256 newImpact) external onlyOwner {        \\n        emit UpdateImpact(impact, newImpact);\\n        impact = newImpact;\\n    }\\n```\\n
Vulnerability: In over liquidation, if the liquidatee has USDC-denominated assets for sale, the liquidator can buy the assets with USDC to avoid paying USDC to the liquidatee
Severity: medium

Description:
In over liquidation, if the liquidatee has USDC-denominated assets for sale, the liquidator can buy the assets with USDC to avoid paying USDC to the liquidatee\\nIn JUSDBank contract, if the liquidator wants to liquidate more collateral than the borrowings of the liquidatee, the liquidator can pay additional USDC to get the liquidatee's collateral.\\n```\\n        } else {\\n            //            actualJUSD = actualCollateral * priceOff\\n            //            = JUSDBorrowed * priceOff / priceOff * (1-insuranceFeeRate)\\n            //            = JUSDBorrowed / (1-insuranceFeeRate)\\n            //            insuranceFee = actualJUSD * insuranceFeeRate\\n            //            = actualCollateral * priceOff * insuranceFeeRate\\n            //            = JUSDBorrowed * insuranceFeeRate / (1- insuranceFeeRate)\\n            liquidateData.actualCollateral = JUSDBorrowed\\n                .decimalDiv(priceOff)\\n                .decimalDiv(JOJOConstant.ONE - reserve.insuranceFeeRate);\\n            liquidateData.insuranceFee = JUSDBorrowed\\n                .decimalMul(reserve.insuranceFeeRate)\\n                .decimalDiv(JOJOConstant.ONE - reserve.insuranceFeeRate);\\n            liquidateData.actualLiquidatedT0 = liquidatedInfo.t0BorrowBalance;\\n            liquidateData.actualLiquidated = JUSDBorrowed;\\n        }\\n\\n        liquidateData.liquidatedRemainUSDC = (amount -\\n            liquidateData.actualCollateral).decimalMul(price);\\n```\\n\\nThe liquidator needs to pay USDC in the callback and the JUSDBank contract will require the final USDC balance of the liquidatee to increase.\\n```\\n        require(\\n            IERC20(primaryAsset).balanceOf(liquidated) -\\n                primaryLiquidatedAmount >=\\n                liquidateData.liquidatedRemainUSDC,\\n            JUSDErrors.LIQUIDATED_AMOUNT_NOT_ENOUGH\\n        );\\n```\\n\\nIf the liquidatee has USDC-denominated assets for sale, the liquidator can purchase the assets with USDC in the callback, so that the liquidatee's USDC balance will increase and the liquidator will not need to send USDC to the liquidatee to pass the check in the JUSDBank contract.

Impact:
In case of over liquidation, the liquidator does not need to pay additional USDC to the liquidatee

Recommended mitigation:
Consider banning over liquidation
Vulnerability: FlashLoanLiquidate.JOJOFlashLoan has no slippage control when swapping USDC
Severity: medium

Description:
FlashLoanLiquidate.JOJOFlashLoan has no slippage control when swapping USDC\\nIn both GeneralRepay.repayJUSD and FlashLoanRepay.JOJOFlashLoan, the user-supplied minReceive parameter is used for slippage control when swapping USDC.\\n```\\n    function JOJOFlashLoan(\\n        address asset,\\n        uint256 amount,\\n        address to,\\n        bytes calldata param\\n    ) external {\\n        (address approveTarget, address swapTarget, uint256 minReceive, bytes memory data) = abi\\n            .decode(param, (address, address, uint256, bytes));\\n        IERC20(asset).approve(approveTarget, amount);\\n        (bool success, ) = swapTarget.call(data);\\n        if (success == false) {\\n            assembly {\\n                let ptr := mload(0x40)\\n                let size := returndatasize()\\n                returndatacopy(ptr, 0, size)\\n                revert(ptr, size)\\n            }\\n        }\\n        uint256 USDCAmount = IERC20(USDC).balanceOf(address(this));\\n        require(USDCAmount >= minReceive, "receive amount is too small");\\n// rest of code\\n    function repayJUSD(\\n        address asset,\\n        uint256 amount,\\n        address to,\\n        bytes memory param\\n    ) external {\\n        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);\\n        uint256 minReceive;\\n        if (asset != USDC) {\\n            (address approveTarget, address swapTarget, uint256 minAmount, bytes memory data) = abi\\n                .decode(param, (address, address, uint256, bytes));\\n            IERC20(asset).approve(approveTarget, amount);\\n            (bool success, ) = swapTarget.call(data);\\n            if (success == false) {\\n                assembly {\\n                    let ptr := mload(0x40)\\n                    let size := returndatasize()\\n                    returndatacopy(ptr, 0, size)\\n                    revert(ptr, size)\\n                }\\n            }\\n            minReceive = minAmount;\\n        }\\n\\n        uint256 USDCAmount = IERC20(USDC).balanceOf(address(this));\\n        require(USDCAmount >= minReceive, "receive amount is too small");\\n```\\n\\nHowever, this is not done in FlashLoanLiquidate.JOJOFlashLoan, and the lack of slippage control may expose the user to sandwich attacks when swapping USDC.

Impact:
The lack of slippage control may expose the user to sandwich attacks when swapping USDC.

Recommended mitigation:
Consider making FlashLoanLiquidate.JOJOFlashLoan use the minReceive parameter for slippage control when swapping USDC.
Vulnerability: JUSDBank users can bypass individual collateral borrow limits
Severity: medium

Description:
JUSDBank imposes individual borrow caps on each collateral. The issue is that this can be bypassed due to the fact that withdraw and borrow use different methods to determine if an account is safe.\\n```\\n    function borrow(\\n        uint256 amount,\\n        address to,\\n        bool isDepositToJOJO\\n    ) external override nonReentrant nonFlashLoanReentrant{\\n        //     t0BorrowedAmount = borrowedAmount /  getT0Rate\\n        DataTypes.UserInfo storage user = userInfo[msg.sender];\\n        _borrow(user, isDepositToJOJO, to, amount, msg.sender);\\n        require(\\n            _isAccountSafeAfterBorrow(user, getTRate()),\\n            JUSDErrors.AFTER_BORROW_ACCOUNT_IS_NOT_SAFE\\n        );\\n    }\\n```\\n\\nWhen borrowing the contract calls _isAccountSafeAfterBorrow. This imposes a max borrow on each collateral type that guarantees that the user cannot borrow more than the max for each collateral type. The issues is that withdraw doesn't impose this cap. This allows a user to bypass this cap as shown in the example below.\\nExample: Assume WETH and WBTC both have a cap of 10,000 borrow. The user deposits $30,000 WETH and takes a flashloand for $30,000 WBTC. Now they deposit both and borrow 20,000 JUSD. They then withdraw all their WBTC to repay the flashloan and now they have borrowed 20,000 against $30000 in WETH

Impact:
Deposit caps can be easily surpassed creating systematic risk for the system

Recommended mitigation:
Always use _isAccountSafeAfterBorrow
Vulnerability: GeneralRepay#repayJUSD returns excess USDC to `to` address rather than msg.sender
Severity: medium

Description:
When using GeneralRepay#repayJUSD `to` repay a position on JUSDBank, any excess tokens are sent `to` the `to` address. While this is fine for users that are repaying their own debt this is not good when repaying for another user. Additionally, specifying an excess `to` repay is basically a requirement when attempting `to` pay off the entire balance of an account. This combination of factors will make it very likely that funds will be refunded incorrectly.\\n```\\n        IERC20(USDC).approve(jusdExchange, borrowBalance);\\n        IJUSDExchange(jusdExchange).buyJUSD(borrowBalance, address(this));\\n        IERC20(USDC).safeTransfer(to, USDCAmount - borrowBalance);\\n        JUSDAmount = borrowBalance;\\n    }\\n```\\n\\nAs seen above, when there is an excess amount of USDC, it is transferred `to` the `to` address which is the recipient of the repay. When `to` != msg.sender all excess will be sent `to` the recipient of the repay rather than being refunded `to` the caller.

Impact:
Refund is sent to the wrong address if to != msg.sender

Recommended mitigation:
Either send the excess back to the caller or allow them to specify where the refund goes
Vulnerability: Certain ERC20 token does not return bool from approve and transfer and transaction revert
Severity: medium

Description:
Certain ERC20 token does not return bool from approve and transfer and transaction revert\\nAccording to\\nSome tokens do not return a bool on ERC20 methods and use IERC20 token interface will revert transaction\\nCertain ERC20 token does not return bool from approve and transfer and transaction revert\\n```\\n   function setApprovalForERC20(\\n        IERC20 erc20Contract,\\n        address to,\\n        uint256 amount\\n    ) external onlyClubOwner {\\n        erc20Contract.approve(to, amount);\\n    }\\n```\\n\\nand\\n```\\nfunction transferERC20(\\n IERC20 erc20Contract,\\n address to,\\n uint256 amount\\n) external onlyClubOwner {\\n erc20Contract.transfer(to, amount);\\n}\\n```\\n\\nthe transfer / approve can fail slienlty

Impact:
Some tokens do not return a bool on ERC20 methods and use IERC20 token interface will revert transaction

Recommended mitigation:
Issue Certain ERC20 token does not return bool from approve and transfer and transaction revert\\nUse Openzeppelin SafeTransfer / SafeApprove
Vulnerability: Users might lose funds as `claimERC20Prize()` doesn't revert for no-revert-on-transfer tokens
Severity: medium

Description:
Users can call `claimERC20Prize()` without actually receiving tokens if a no-revert-on-failure token is used, causing a portion of their claimable tokens to become unclaimable.\\nIn the `FootiumPrizeDistributor` contract, whitelisted users can call `claimERC20Prize()` to claim ERC20 tokens. The function adds the amount of tokens claimed to the user's total claim amount, and then transfers the tokens to the user:\\nFootiumPrizeDistributor.sol#L128-L131\\n```\\nif (value > 0) {\\n    totalERC20Claimed[_token][_to] += value;\\n    _token.transfer(_to, value);\\n}\\n```\\n\\nAs the the return value from `transfer()` is not checked, `claimERC20Prize()` does not revert even when the transfer of tokens to the user fails.\\nThis could potentially cause users to lose assets when:\\n`_token` is a no-revert-on-failure token.\\nThe user calls `claimERC20Prize()` with `value` higher than the contract's token balance.\\nAs the contract has an insufficient balance, `transfer()` will revert and the user receives no tokens. However, as `claimERC20Prize()` succeeds, `totalERC20Claimed` is permanently increased for the user, thus the user cannot claim these tokens again.

Impact:
Users can call `claimERC20Prize()` without receiving the token amount specified. These tokens become permanently unclaimable for the user, leading to a loss of funds.

Recommended mitigation:
Use `safeTransfer()` from Openzeppelin's SafeERC20 to transfer ERC20 tokens. Note that `transferERC20()` in `FootiumEscrow.sol` also uses `transfer()` and is susceptible to the same vulnerability.
Vulnerability: Users can bypass Player royalties on EIP2981 compatible markets by selling clubs as a whole
Severity: medium

Description:
Players have a royalty built in but clubs do not. This allows bulk sale of players via clubs to bypass the fee when selling players.\\nFootiumPlayer.sol#L16-L23\\n```\\ncontract FootiumPlayer is\\n    ERC721Upgradeable,\\n    AccessControlUpgradeable,\\n    ERC2981Upgradeable,\\n    PausableUpgradeable,\\n    ReentrancyGuardUpgradeable,\\n    OwnableUpgradeable\\n{\\n```\\n\\nFootiumPlayer implements the EIP2981 standard which creates fees when buy/selling the players.\\nFootiumClub.sol#L15-L21\\n```\\ncontract FootiumClub is\\n    ERC721Upgradeable,\\n    AccessControlUpgradeable,\\n    PausableUpgradeable,\\n    ReentrancyGuardUpgradeable,\\n    OwnableUpgradeable\\n{\\n```\\n\\nFootiumClub on the other hand never implements this standard. This allows users to sell players by selling their club to avoid any kind of fee on player sales.

Impact:
Users can bypass fees on player sales by selling club instead

Recommended mitigation:
Implement EIP2981 on clubs as well
Vulnerability: Merkle leaf values for _clubDivsMerkleRoot are 64 bytes before hashing which can lead to merkle tree collisions
Severity: medium

Description:
FootiumAcademy hashes 64 bytes when calculating leaf allowing it to collide with the internal nodes of the merkle tree.\\nMerkleProofUpgradeable.sol puts the following warning at the beginning of the contract:\\n```\\n * WARNING: You should avoid using leaf values that are 64 bytes long prior to\\n * hashing, or use a hash function other than keccak256 for hashing leaves.\\n * This is because the concatenation of a sorted pair of internal nodes in\\n * the merkle tree could be reinterpreted as a leaf value.\\n```\\n\\nFootiumAcademy.sol#L235-L240\\n```\\n    if (\\n        !MerkleProofUpgradeable.verify(\\n            divisionProof,\\n            _clubDivsMerkleRoot,\\n            keccak256(abi.encodePacked(clubId, divisionTier)) <- @audit-issue 64 bytes before hashing allows collisions with internal nodes\\n        )\\n```\\n\\nThis is problematic because FootiumAcademy uses clubId and divisionTier as the base of the leaf, which are both uint256 (32 bytes each for 64 bytes total). This allows collision between leaves and internal nodes. These collisions could allow users to mint to divisions that otherwise would be impossible.

Impact:
Users can abuse merkle tree collisions to mint in non-existent divisions and bypass minting fees

Recommended mitigation:
Use a combination of variables that doesn't sum to 64 bytes
Vulnerability: AuraSpell#openPositionFarm uses incorrect join type for balancer
Severity: high

Description:
The JoinPoolRequest uses "" for userData meaning that it will decode into 0. This is problematic because join requests of type 0 are "init" type joins and will revert for pools that are already initialized.\\n```\\nenum JoinKind { INIT, EXACT_TOKENS_IN_FOR_BPT_OUT, TOKEN_IN_FOR_EXACT_BPT_OUT }\\n```\\n\\nWe see above that enum JoinKind is INIT for 0 values.\\n```\\n        return _joinExactTokensInForBPTOut(balances, normalizedWeights, userData);\\n    } else if (kind == JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT) {\\n        return _joinTokenInForExactBPTOut(balances, normalizedWeights, userData);\\n    } else {\\n        _revert(Errors.UNHANDLED_JOIN_KIND);\\n    }\\n```\\n\\nHere user data is decoded into join type and since it is "" it will decode to type 0 which will result in a revert.

Impact:
Users will be unable to open any farm position on AuraSpell

Recommended mitigation:
Uses JoinKind = 1 for user data