msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Malicious user can frontrun withdrawals from Insurance Fund to significantly decrease value of shares
Severity: medium

Description:
When a user withdraws from the insurance fund, the value of their shares is calculated based on the balance of vUSD in the fund. Another user could deliberately frontrun (or frontrun by chance) the withdrawal with a call to `settleBadDebt` to significantly reduce the vUSD returned from the withdrawal with the same number of shares.\\nWhen a user wants to `withdraw` from the insurance pool they have to go through a 2 step withdrawal process. First they need to unbond their shares, and then they have to wait for the pre-determined unbonding period before they can `withdraw` the vUSD their shares are worth by calling `withdraw`.\\nWhen a user calls `withdraw` the amount of vUSD to redeem is calculated as:\\n```\\namount = balance() * shares / totalSupply();\\n```\\n\\nwhere `balance()` is the balance of vUSD in the contract and `totalSupply()` is the total supply of share tokens. Therefore, if the balance of vUSD in the contract were to decrease, then the amount of vUSD redeemed from the same number of shares would decrease as a result.\\nThis occurs when a trader's bad debt is settled when calling `settleBadDebt` in `MarginAccount.sol` as this calls `insuranceFund.seizeBadDebt` under the hood, which in turn calls `settlePendingObligation` which transfers vUSD out of the insurance fund to the margin account:\\n```\\nvusd.safeTransfer(marginAccount, toTransfer);\\n```\\n\\nThe result is now that the balance of vUSD in the insurance fund is lower and thus the shares are worth less vUSD as a consequence.

Impact:
A user withdrawing from the insurance fund could receive significantly less (potentially 0) vUSD when finalising their withdrawal.

Recommended mitigation:
One option would be to include a slippage parameter on the `withdraw` and `withdrawFor` methods so that the user redeeming shares can specify the minimum amount of vUSD they would accept for their shares.\\nWhen depositing into the insurance fund, the number of shares to mint is actually calculated based on the total value of the pool (value of vUSD and all other collateral assets). Therefore, the withdraw logic could also use `_totalPoolValue` instead of `balance()` to get a "true" value per share, however this could lead to withdrawals failing while assets are up for auction. Assuming all the assets are expected to be sold within the short 2 hour auction duration, this is probably the better solution given the pricing is more accurate, but it depends if users would accept failed withdrawals for short periods of time.
Vulnerability: min withdraw of 5 VUSD is not enough to prevent DOS via VUSD.sol#withdraw(amount)
Severity: medium

Description:
A vulnerability exists where a malicious user spam the contract with numerous withdrawal requests (e.g., 5,000). This would mean that genuine users who wish to withdraw their funds may find themselves unable to do so in a timely manner because the processing of their withdrawals could be delayed significantly.\\nThe issue stems from the fact that there is no restriction on the number of withdrawal requests a single address can make. A malicious actor could repeatedly call the withdraw or withdrawTo function, each time with a small amount (min 5 VUSD), to clog the queue with their withdrawal requests.\\n```\\n    //E Burn vusd from msg.sender and queue the withdrawal to "to" address\\n    function _withdrawTo(address to, uint amount) internal {\\n        //E check min amount\\n        require(amount >= 5 * (10 ** 6), "min withdraw is 5 vusd"); //E @audit-info not enough to prevent grief\\n        //E burn this amount from msg.sender\\n        burn(amount); // burn vusd from msg.sender\\n        //E push \\n        withdrawals.push(Withdrawal(to, amount * 1e12));\\n    }\\n```\\n\\nGiven the maxWithdrawalProcesses is set to 100, and the withdrawal processing function processWithdrawals doesn't have any parameter to process from a specific index in the queue, only the first 100 requests in the queue would be processed at a time.\\n```\\n    uint public maxWithdrawalProcesses = 100;\\n    //E create array of future withdrawal that will be executed to return\\n    function withdrawalQueue() external view returns(Withdrawal[] memory queue) {\\n        //E check if more than 100 requests in withdrawals array\\n        uint l = _min(withdrawals.length-start, maxWithdrawalProcesses);\\n        queue = new Withdrawal[](l);\\n\\n        for (uint i = 0; i < l; i++) {\\n            queue[i] = withdrawals[start+i];\\n        }\\n    }\\n```\\n\\nIn the case of an attack, the first 100 withdrawal requests could be those of the attacker, meaning that the genuine users' requests would be stuck in the queue until all of the attacker's requests have been processed. Moreover the fact that we can only withdraw up to 1 day long when our withdraw request is good to go.

Impact:
This could result in significant delays for genuine users wanting to withdraw their funds, undermining the contract's usability and users' trust in the platform.

Recommended mitigation:
Either limit number of withdrawal requests per address could be a first layer of defense even if it's not enough but I don't see the point why this limit is included so removing it could mitigate this. Otherwise you could implement a priority queue regarding amount to be withdrawn
Vulnerability: Malicious user can grief withdrawing users via VUSD reentrancy
Severity: medium

Description:
VUSD#processWithdraw makes a call to withdrawal.usr to send the withdrawn gas token. processWithdrawals is the only nonreentrant function allowing a user to create a smart contract that uses it's receive function to deposit then immediately withdraw to indefinitely lengthen the withdrawal queue and waste large amounts of caller gas.\\nVUSD.sol#L69-L77\\n```\\n    while (i < withdrawals.length && (i - start) < maxWithdrawalProcesses) {\\n        Withdrawal memory withdrawal = withdrawals[i];\\n        if (reserve < withdrawal.amount) {\\n            break;\\n        }\\n\\n        (bool success, bytes memory data) = withdrawal.usr.call{value: withdrawal.amount}("");\\n        if (success) {\\n            reserve -= withdrawal.amount;\\n```\\n\\nTo send the withdrawn gas token to the user VUSD#processWithdrawals utilizes a call with no data. When received by a contract this will trigger it's receive function. This can be abused to continually grief users who withdraw with no recurring cost to the attacker. To exploit this the attacker would withdraw VUSD to a malicious contract. This contract would deposit the received gas token then immediately withdraw it. This would lengthen the queue. Since the queue is first-in first-out a user would be forced to process all the malicious withdrawals before being able to process their own. While processing them they would inevitably reset the grief for the next user.\\nNOTE: I am submitting this as a separate issue apart from my other two similar issues. I believe it should be a separate issue because even though the outcome is similar the root cause is entirely different. Those are directly related to the incorrect call parameters while the root cause of this issue is that both mintWithReserve and withdraw/withdrawTo lack the reentrant modifier allowing this malicious reentrancy.

Impact:
Malicious user can maliciously reenter VUSD to grief users via unnecessary gas wastage

Recommended mitigation:
Add the nonreentrant modifer to mintWithReserve withdraw and withdrawTo
Vulnerability: Malicious users can donate/leave dust amounts of collateral in contract during auctions to buy other collateral at very low prices
Severity: medium

Description:
Auctions are only ended early if the amount of the token being auctioned drops to 0. This can be exploited via donation or leaving dust in the contract to malicious extend the auction and buy further liquidate collateral at heavily discounted prices.\\nInsuranceFund.sol#L184-L199\\n```\\nfunction buyCollateralFromAuction(address token, uint amount) override external {\\n    Auction memory auction = auctions[token];\\n    // validate auction\\n    require(_isAuctionOngoing(auction.startedAt, auction.expiryTime), "IF.no_ongoing_auction");\\n\\n    // transfer funds\\n    uint vusdToTransfer = _calcVusdAmountForAuction(auction, token, amount);\\n    address buyer = _msgSender();\\n    vusd.safeTransferFrom(buyer, address(this), vusdToTransfer);\\n    IERC20(token).safeTransfer(buyer, amount); // will revert if there wasn't enough amount as requested\\n\\n    // close auction if no collateral left\\n    if (IERC20(token).balanceOf(address(this)) == 0) { <- @audit-issue only cancels auction if balance = 0\\n        auctions[token].startedAt = 0;\\n    }\\n}\\n```\\n\\nWhen buying collateral from an auction, the auction is only closed if the balance of the token is 0. This can be exploited in a few ways to maliciously extend auctions and keep the timer (and price) decreasing. The first would be buy all but 1 wei of a token leaving it in the contract so the auction won't close. Since 1 wei isn't worth the gas costs to buy, there would be a negative incentive to buy the collateral, likely resulting in no on buying the final amount. A second approach would be to frontrun an buys with a single wei transfer with the same results.\\nNow that the auction has been extended any additional collateral added during the duration of the auction will start immediately well below the assets actual value. This allows malicious users to buy the asset for much cheaper, causing loss to the insurance fund.

Impact:
Users can maliciously extend auctions and potentially get collateral for very cheap

Recommended mitigation:
Close the auction if there is less than a certain threshold of a token remaining after it has been bought:\\n```\\n    IERC20(token).safeTransfer(buyer, amount); // will revert if there wasn't enough amount as requested\\n\\n+   uint256 minRemainingBalance = 1 * 10 ** (IERC20(token).decimal() - 3);\\n\\n    // close auction if no collateral left\\n+   if (IERC20(token).balanceOf(address(this)) <= minRemainingBalance) {\\n        auctions[token].startedAt = 0;\\n    }\\n```\\n
Vulnerability: MarginAccountHelper will be bricked if registry.marginAccount or insuranceFund ever change
Severity: medium

Description:
MarginAccountHelper#syncDeps causes the contract to refresh it's references to both marginAccount and insuranceFund. The issue is that approvals are never made to the new contracts rendering them useless.\\nMarginAccountHelper.sol#L82-L87\\n```\\nfunction syncDeps(address _registry) public onlyGovernance {\\n    IRegistry registry = IRegistry(_registry);\\n    vusd = IVUSD(registry.vusd());\\n    marginAccount = IMarginAccount(registry.marginAccount());\\n    insuranceFund = IInsuranceFund(registry.insuranceFund());\\n}\\n```\\n\\nWhen syncDeps is called the marginAccount and insuranceFund references are updated. All transactions require approvals to one of those two contract. Since no new approvals are made, the contract will become bricked and all transactions will revert.

Impact:
Contract will become bricked and all contracts that are integrated or depend on it will also be bricked

Recommended mitigation:
Remove approvals to old contracts before changing and approve new contracts after
Vulnerability: setSymbolsPrice() can use the priceSig from a long time ago
Severity: high

Description:
`setSymbolsPrice()` only restricts the maximum value of `priceSig.timestamp`, but not the minimum time This allows a malicious user to choose a malicious `priceSig` from a long time ago A malicious `priceSig.upnl` can seriously harm `partyB`\\n`setSymbolsPrice()` only restricts the maximum value of `priceSig.timestamp`, but not the minimum time\\n```\\n    function setSymbolsPrice(address partyA, PriceSig memory priceSig) internal {\\n        MAStorage.Layout storage maLayout = MAStorage.layout();\\n        AccountStorage.Layout storage accountLayout = AccountStorage.layout();\\n      LibMuon.verifyPrices(priceSig, partyA);\\n        require(\\n          priceSig.timestamp <=\\n                maLayout.liquidationTimestamp[partyA] + maLayout.liquidationTimeout,\\n            "LiquidationFacet: Expired signature"\\n        );\\n```\\n\\nLibMuon.verifyPrices only check sign, without check the time range\\n```\\n    function verifyPrices(PriceSig memory priceSig, address partyA) internal view {\\n        MuonStorage.Layout storage muonLayout = MuonStorage.layout();\\n        require(priceSig.prices.length == priceSig.symbolIds.length, "LibMuon: Invalid length");\\n        bytes32 hash = keccak256(\\n            abi.encodePacked(\\n                muonLayout.muonAppId,\\n                priceSig.reqId,\\n                address(this),\\n                partyA,\\n                priceSig.upnl,\\n                priceSig.totalUnrealizedLoss,\\n                priceSig.symbolIds,\\n                priceSig.prices,\\n                priceSig.timestamp,\\n                getChainId()\\n            )\\n        );\\n        verifyTSSAndGateway(hash, priceSig.sigs, priceSig.gatewaySignature);\\n    }\\n```\\n\\nIn this case, a malicious user may pick any `priceSig` from a long time ago, and this `priceSig` may have a large negative `unpl`, leading to `LiquidationType.OVERDUE`, severely damaging `partyB`\\nWe need to restrict `priceSig.timestamp` to be no smaller than `maLayout.liquidationTimestamp[partyA]` to avoid this problem

Impact:
Maliciously choosing the illegal `PriceSig` thus may hurt others user

Recommended mitigation:
restrict `priceSig.timestamp` to be no smaller than `maLayout.liquidationTimestamp[partyA]`\\n```\\n    function setSymbolsPrice(address partyA, PriceSig memory priceSig) internal {\\n        MAStorage.Layout storage maLayout = MAStorage.layout();\\n        AccountStorage.Layout storage accountLayout = AccountStorage.layout();\\n\\n        LibMuon.verifyPrices(priceSig, partyA);\\n        require(maLayout.liquidationStatus[partyA], "LiquidationFacet: PartyA is solvent");\\n        require(\\n            priceSig.timestamp <=\\n                maLayout.liquidationTimestamp[partyA] + maLayout.liquidationTimeout,\\n            "LiquidationFacet: Expired signature"\\n        );\\n+     require(priceSig.timestamp >= maLayout.liquidationTimestamp[partyA],"invald price timestamp");\\n```\\n
Vulnerability: Liquidating pending quotes doesn't return trading fee to party A
Severity: medium

Description:
When a user is liquidated, the trading fees of the pending quotes aren't returned.\\nWhen a pending/locked quote is canceled, the trading fee is sent back to party A, e.g.\\nBut, when a pending quote is liquidated, the trading fee is not used for the liquidation. Instead, the fee collector keeps the funds:\\n```\\n    function liquidatePendingPositionsPartyA(address partyA) internal {\\n        QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout();\\n        require(\\n            MAStorage.layout().liquidationStatus[partyA],\\n            "LiquidationFacet: PartyA is solvent"\\n        );\\n        for (uint256 index = 0; index < quoteLayout.partyAPendingQuotes[partyA].length; index++) {\\n            Quote storage quote = quoteLayout.quotes[\\n                quoteLayout.partyAPendingQuotes[partyA][index]\\n            ];\\n            if (\\n                (quote.quoteStatus == QuoteStatus.LOCKED ||\\n                    quote.quoteStatus == QuoteStatus.CANCEL_PENDING) &&\\n                quoteLayout.partyBPendingQuotes[quote.partyB][partyA].length > 0\\n            ) {\\n                delete quoteLayout.partyBPendingQuotes[quote.partyB][partyA];\\n                AccountStorage\\n                .layout()\\n                .partyBPendingLockedBalances[quote.partyB][partyA].makeZero();\\n            }\\n            quote.quoteStatus = QuoteStatus.LIQUIDATED;\\n            quote.modifyTimestamp = block.timestamp;\\n        }\\n        AccountStorage.layout().pendingLockedBalances[partyA].makeZero();\\n        delete quoteLayout.partyAPendingQuotes[partyA];\\n    }\\n```\\n\\n```\\n    function liquidatePartyB(\\n        address partyB,\\n        address partyA,\\n        SingleUpnlSig memory upnlSig\\n    ) internal {\\n        // // rest of code\\n        uint256[] storage pendingQuotes = quoteLayout.partyAPendingQuotes[partyA];\\n\\n        for (uint256 index = 0; index < pendingQuotes.length; ) {\\n            Quote storage quote = quoteLayout.quotes[pendingQuotes[index]];\\n            if (\\n                quote.partyB == partyB &&\\n                (quote.quoteStatus == QuoteStatus.LOCKED ||\\n                    quote.quoteStatus == QuoteStatus.CANCEL_PENDING)\\n            ) {\\n                accountLayout.pendingLockedBalances[partyA].subQuote(quote);\\n\\n                pendingQuotes[index] = pendingQuotes[pendingQuotes.length - 1];\\n                pendingQuotes.pop();\\n                quote.quoteStatus = QuoteStatus.LIQUIDATED;\\n                quote.modifyTimestamp = block.timestamp;\\n            } else {\\n                index++;\\n            }\\n        }\\n```\\n\\nThese funds should be used to cover the liquidation. Since no trade has been executed, the fee collector shouldn't earn anything.

Impact:
Liquidation doesn't use paid trading fees to cover outstanding balances. Instead, the funds are kept by the fee collector.

Recommended mitigation:
return the funds to party A. If party A is being liquidated, use the funds to cover the liquidation. Otherwise, party A keeps the funds.
Vulnerability: In case if trading fee will be changed then refund will be done with wrong amount
Severity: medium

Description:
In case if trading fee will be changed then refund will be done with wrong amount\\nWhen user creates quote, then he pays trading fees. Amount that should be paid is calculated inside `LibQuote.getTradingFee` function.\\n```\\n    function getTradingFee(uint256 quoteId) internal view returns (uint256 fee) {\\n        QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout();\\n        Quote storage quote = quoteLayout.quotes[quoteId];\\n        Symbol storage symbol = SymbolStorage.layout().symbols[quote.symbolId];\\n        if (quote.orderType == OrderType.LIMIT) {\\n            fee =\\n                (LibQuote.quoteOpenAmount(quote) * quote.requestedOpenPrice * symbol.tradingFee) /\\n                1e36;\\n        } else {\\n            fee = (LibQuote.quoteOpenAmount(quote) * quote.marketPrice * symbol.tradingFee) / 1e36;\\n        }\\n    }\\n```\\n\\nAs you can see `symbol.tradingFee` is used to determine fee amount. This fee can be changed any time.\\nWhen order is canceled, then fee should be returned to user. This function also uses `LibQuote.getTradingFee` function to calculate fee to return.\\nSo in case if order was created before fee changes, then returned amount will be not same, when it is canceled after fee changes.

Impact:
User or protocol losses portion of funds.

Recommended mitigation:
You can store fee paid by user inside quote struct. And when canceled return that amount.
Vulnerability: lockQuote() increaseNonce parameters do not work properly
Severity: medium

Description:
in `lockQuote()` will execute `partyBNonces[quote.partyB][quote.partyA] += 1` if increaseNonce == true But this operation is executed before setting `quote.partyB`, resulting in actually setting `partyBNonces[address(0)][quote.partyA] += 1`\\nin `lockQuote()` , when execute `partyBNonces[quote.partyB][quote.partyA] += 1` , `quote.paryB` is address(0)\\n```\\n    function lockQuote(uint256 quoteId, SingleUpnlSig memory upnlSig, bool increaseNonce) internal {\\n        QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout();\\n        AccountStorage.Layout storage accountLayout = AccountStorage.layout();\\n\\n        Quote storage quote = quoteLayout.quotes[quoteId];\\n        LibMuon.verifyPartyBUpnl(upnlSig, msg.sender, quote.partyA);\\n        checkPartyBValidationToLockQuote(quoteId, upnlSig.upnl);\\n        if (increaseNonce) {\\n          accountLayout.partyBNonces[quote.partyB][quote.partyA] += 1;\\n        }\\n        quote.modifyTimestamp = block.timestamp;\\n        quote.quoteStatus = QuoteStatus.LOCKED;\\n      quote.partyB = msg.sender;\\n        // lock funds for partyB\\n        accountLayout.partyBPendingLockedBalances[msg.sender][quote.partyA].addQuote(quote);\\n        quoteLayout.partyBPendingQuotes[msg.sender][quote.partyA].push(quote.id);\\n    }\\n```\\n\\nactually setting `partyBNonces[address(0)][quote.partyA] += 1`

Impact:
increaseNonce parameters do not work properly

Recommended mitigation:
```\\n    function lockQuote(uint256 quoteId, SingleUpnlSig memory upnlSig, bool increaseNonce) internal {\\n        QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout();\\n        AccountStorage.Layout storage accountLayout = AccountStorage.layout();\\n\\n        Quote storage quote = quoteLayout.quotes[quoteId];\\n        LibMuon.verifyPartyBUpnl(upnlSig, msg.sender, quote.partyA);\\n        checkPartyBValidationToLockQuote(quoteId, upnlSig.upnl);\\n        if (increaseNonce) {\\n-           accountLayout.partyBNonces[quote.partyB][quote.partyA] += 1;\\n+           accountLayout.partyBNonces[msg.sender][quote.partyA] += 1;\\n        }\\n        quote.modifyTimestamp = block.timestamp;\\n        quote.quoteStatus = QuoteStatus.LOCKED;\\n        quote.partyB = msg.sender;\\n        // lock funds for partyB\\n        accountLayout.partyBPendingLockedBalances[msg.sender][quote.partyA].addQuote(quote);\\n        quoteLayout.partyBPendingQuotes[msg.sender][quote.partyA].push(quote.id);\\n    }\\n```\\n
Vulnerability: Wrong calculation of solvency after request to close and after close position
Severity: medium

Description:
`isSolventAfterClosePosition` and `isSolventAfterRequestToClosePosition` do not account for the extra profit that the user would get from closing the position.\\nWhen a party A creates a request for closing a position, the `isSolventAfterRequestToClosePosition` function is called to check if the user is solvent after the request. In the same way, when someone tries to close a position, the `isSolventAfterClosePosition` function is called to check if both party A and party B are solvent after closing the position.\\nBoth functions calculate the available balance for party A and party B, and revert if it is lower than zero. After that, the function accounts for the the extra loss that the user would get as a result of the difference between `closePrice` and `upnlSig.price`, and checks if the user is solvent after that.\\nThe problem is that the function does not account for the opposite case, that is the case where the user would get an extra profit as a result of the difference between `closePrice` and `upnlSig.price`. This means that the user would not be able to close the position, even if at the end of the transaction they would be solvent.\\nProof of Concept\\nThere is an open position with:\\nPosition type: LONG\\nQuantity: 1\\nLocked: 50\\nOpened price: 100\\nCurrent price: 110\\nQuote position uPnL Party A: 10\\nParty B calls `fillCloseRequest` with:\\nClosed price: 120\\nIn `isSolventAfterClosePosition` the following is calculated:\\n```\\npartyAAvailableBalance = freeBalance + upnl + unlockedAmount = -5\\n```\\n\\nAnd it reverts on:\\n```\\nrequire(\\n    partyBAvailableBalance >= 0 && partyAAvailableBalance >= 0,\\n    "LibSolvency: Available balance is lower than zero"\\n);\\n```\\n\\nHowever, the extra profit for `closedPrice - upnlSig.price = 120 - 110 = 10` is not accounted for in the `partyAAvailableBalance` calculation, that should be `partyAAvailableBalance = - 5 + 10 = 5`. Party A would be solvent after closing the position, but the transaction reverts.

Impact:
In a situation where the difference between the closed price and the current price will make the user solvent, users will not be able to close their positions, even if at the end of the transaction they would be solvent.

Recommended mitigation:
Add the extra profit to the `partyAAvailableBalance` calculation.