msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: LenderCommitmentForwarder#updateCommitment can be front-run by malicious borrower to cause lender to over-commit funds
Severity: medium

Description:
This is the same idea as approve vs increaseAlllowance. updateCommitment is a bit worse though because there are more reason why a user may wish to update their commitment (expiration, collateral ratio, interest rate, etc).\\nLenderCommitmentForwarder.sol#L212-L222\\n```\\n    require(\\n        _commitment.principalTokenAddress ==\\n            commitments[_commitmentId].principalTokenAddress,\\n        "Principal token address cannot be updated."\\n    );\\n    require(\\n        _commitment.marketId == commitments[_commitmentId].marketId,\\n        "Market Id cannot be updated."\\n    );\\n\\n    commitments[_commitmentId] = _commitment;\\n```\\n\\nLenderCommitmentForwarder#updateCommitment overwrites ALL of the commitment data. This means that even if a user is calling it to update even one value the maxPrincipal will reset, opening up the following attack vector:\\nUser A creates a commitment for 100e6 USDC lending against ETH\\nUser A's commitment is close to expiry so they call to update their commitment with a new expiration\\nUser B sees this update and front-runs it with a loan against the commitment for 100e6 USDC\\nUser A's commitment is updated and the amount is set back to 100e6 USDC\\nUser B takes out another loan for 100e6 USDC\\nUser A has now loaned out 200e6 USDC when they only meant to loan 100e6 USDC

Impact:
Commitment is abused to over-commit lender

Recommended mitigation:
Create a function that allows users to extend expiry while keeping amount unchanged. Additionally create a function similar to increaseApproval which increase amount instead of overwriting amount.
Vulnerability: Bid submission vulnerable to market parameters changes
Severity: medium

Description:
The details for the audit state:\\nMarket owners should NOT be able to race-condition attack borrowers or lenders by changing market settings while bids are being submitted or accepted (while tx are in mempool). Care has been taken to ensure that this is not possible (similar in theory to sandwich attacking but worse as if possible it could cause unexpected and non-consentual interest rate on a loan) and further-auditing of this is welcome.\\nHowever, there is little protection in place to protect the submitter of a bid from changes in market parameters.\\nIn _submitBid(), certain bid parameters are taken from the marketRegistry:\\n```\\n    function _submitBid(// rest of code)\\n        // rest of code\\n        (bid.terms.paymentCycle, bidPaymentCycleType[bidId]) = marketRegistry\\n            .getPaymentCycle(_marketplaceId);\\n\\n        bid.terms.APR = _APR;\\n\\n        bidDefaultDuration[bidId] = marketRegistry.getPaymentDefaultDuration(\\n            _marketplaceId\\n        );\\n\\n        bidExpirationTime[bidId] = marketRegistry.getBidExpirationTime(\\n            _marketplaceId\\n        );\\n\\n        bid.paymentType = marketRegistry.getPaymentType(_marketplaceId);\\n        \\n        bid.terms.paymentCycleAmount = V2Calculations\\n            .calculatePaymentCycleAmount(\\n                bid.paymentType,\\n                bidPaymentCycleType[bidId],\\n                _principal,\\n                _duration,\\n                bid.terms.paymentCycle,\\n                _APR\\n            );\\n        // rest of code\\n```\\n

Impact:
If market parameters are changed in between the borrower submitting a bid transaction and the transaction being applied, borrower may be subject to changes in `bidDefaultDuration`, `bidExpirationTime`, `paymentType`, `paymentCycle`, `bidPaymentCycleType` and `paymentCycleAmount`.\\nThat is, the user may be committed to the bid for longer / shorter than expected. They may have a longer / shorter default duration (time for the loan being considered defaulted / eligible for liquidation). They have un-provisioned for payment type and cycle parameters.\\nI believe most of this will have a medium impact on borrower (mild inconveniences / resolvable by directly repaying the loan) if the market owner is not evil and adapting the parameters reasonably.\\nAn evil market owner can set the value of `bidDefaultDuration` and `paymentCycle` very low (0) so that the loan will default immediately. It can then accept the bid, make user default immediately, and liquidate the loan to steal the user's collateral. This results in a loss of collateral for the borrower.

Recommended mitigation:
Take every single parameters as input of `_submitBid()` (including fee percents) and compare them to the values in `marketRegistry` to make sure borrower agrees with them, revert if they differ.
Vulnerability: defaulting doesn't change the state of the loan
Severity: medium

Description:
The lender can claim the borrowers collateral in case they have defaulted on their payments. This however does not change the state of the loan so the borrower can continue making payments to the lender even though the loan is defaulted.\\n```\\nFile: CollateralManager.sol\\n\\n       } else if (tellerV2.isLoanDefaulted(_bidId)) {\\n           _withdraw(_bidId, tellerV2.getLoanLender(_bidId)); // sends collateral to lender\\n           emit CollateralClaimed(_bidId);\\n       } else {\\n```\\n\\nSince this is in `CollateralManager` nothing is updating the state kept in `TellerV2` which will still be `ACCEPTED`. The lender could still make payments (in vain).

Impact:
The borrower can continue paying unknowing that the loan is defaulted. The lender could, given a defaulted loan, see that the lender is trying to save their loan and front run the late payment with a seize of collateral. Then get both the late payment and the collateral. This is quite an unlikely scenario though.\\nThe loan will also be left active since even if the borrower pays the `withdraw` of collateral will fail since the collateral is no longer there.

Recommended mitigation:
Remove the possibility for the lender to default the loan in `CollateralManager`. Move defaulting to `TellerV2` so it can properly close the loan.
Vulnerability: Premature Liquidation When a Borrower Pays early
Severity: medium

Description:
On TellerV2 markets, whenever a borrower pays early in one payment cycle, they could be at risk to be liquidated in the next payment cycle. And this is due to a vulnerability in the liquidation logic implemented in `_canLiquidateLoan`. Note: This issue is submitted separately from issue #2 because the exploit is based on user behaviors regardless of a specific market setting. And the vulnerability might warrant a change in the liquidation logic.\\nIn TellerV2.sol, the sole liquidation logic is dependent on the time gap between now and the previous payment timestamp. But a user might decide to pay at any time within a given payment cycle, which makes the time gap unreliable and effectively renders this logic vulnerable to exploitation.\\n```\\n        return (uint32(block.timestamp) -\\n            _liquidationDelay -\\n            lastRepaidTimestamp(_bidId) >\\n            bidDefaultDuration[_bidId]);\\n```\\n\\nSuppose a scenario where a user takes on a loan on a market with 3 days payment cycle and 3 days paymentDefaultDuration. And the loan is 14 days in duration. The user decided to make the first minimal payment an hour after receiving the loan, and the next payment due date is after the sixth day. Now 5 days passed since the user made the first payment, and a liquidator comes in and liquidates the loan and claims the collateral before the second payment is due.\\nHere is a test to show proof of concept for this scenario.

Impact:
Given the fact that this vulnerability is not market specific and that users can pay freely during a payment cycle, it's quite easy for a liquidator to liquidate loans prematurely. And the effect might be across multiple markets.\\nWhen there are proportional collaterals, the exploit can be low cost. An attacker could take on flash loans to pay off the principal and interest, and the interest could be low when early in the loan duration. The attacker would then sell the collateral received in the same transaction to pay off flash loans and walk away with profits.

Recommended mitigation:
Consider using the current timestamp - previous payment due date instead of just `lastRepaidTimestamp` in the liquidation check logic. Also, add the check to see whether a user is late on a payment in `_canLiquidateLoan`.
Vulnerability: Malicious actor can prevent migration by calling a non-existing function in `OVM_L2ToL1MessagePasser` and making `ReadWitnessData` return an error
Severity: medium

Description:
There is a mismatch between collected witness data in l2geth to the parsing of the collected data during migration. The mismatch will return an error and halt the migration until the data will be cleaned.\\nWitness data is collected from L2geth using a state dumper that collects any call to `OVM_L2ToL1MessagePasser`. The data is collected regardless of the calldata itself. Any call to `OVM_L2ToL1MessagePasser` will be collected. The data will persist regardless of the status of the transaction.\\n```\\n func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { \\n  if addr == dump.MessagePasserAddress { \\n   statedumper.WriteMessage(caller.Address(), input) \\n  } \\n```\\n\\nThe data will be stored in a file in the following format: "MSG|<source>|<calldata>"\\nAt the start of the migration process, in order to unpack the message from the calldata, the code uses the first 4 bytes to lookup the the selector of `passMessageToL1` from the calldata and unpack the calldata according to the ABI.\\n```\\n method, err := abi.MethodById(msgB[:4])\\n if err != nil {\\n  return nil, nil, fmt.Errorf("failed to get method: %w", err)\\n }\\n\\n out, err := method.Inputs.Unpack(msgB[4:])\\n if err != nil {\\n  return nil, nil, fmt.Errorf("failed to unpack: %w", err)\\n }\\n```\\n\\nAs can be seen above, the function will return an error that is bubbled up to stop the migration if:\\nThe calldata first 4 bytes is not a selector of a function from the ABI of `OVM_L2ToL1MessagePasser`\\nThe parameters encoded with the selectors are not unpackable (are not the parameters specified by the ABI)\\nA malicious actor will call any non-existing function in the address of `OVM_L2ToL1MessagePasser`. The message will be stored in the witness data and cause an error during migration.\\n`ReadWitnessData` is called to parse they json witness data before any filtering is in place.

Impact:
An arbitrary user can halt the migration process

Recommended mitigation:
Instead of bubbling up an error, simply continue to the next message. This shouldn't cause a problem since in the next stages of the migration there are checks to validate any missing messages from the storage.
Vulnerability: Funds can be stolen because of incorrect update to `ownerToRollOverQueueIndex` for existing rollovers
Severity: high

Description:
In the case where the owner has an existing rollover, the `ownerToRollOverQueueIndex` incorrectly updates to the last queue index. This causes the `notRollingOver` check to be performed on the incorrect `_id`, which then allows the depositor to withdraw funds that should've been locked.\\nIn `enlistInRollover()`, if the user has an existing rollover, it overwrites the existing data:\\n```\\nif (ownerToRollOverQueueIndex[_receiver] != 0) {\\n  // if so, update the queue\\n  uint256 index = getRolloverIndex(_receiver);\\n  rolloverQueue[index].assets = _assets;\\n  rolloverQueue[index].epochId = _epochId;\\n```\\n\\nHowever, regardless of whether the user has an existing rollover, the `ownerToRolloverQueueIndex` points to the last item in the queue:\\n```\\nownerToRollOverQueueIndex[_receiver] = rolloverQueue.length;\\n```\\n\\nThus, the `notRollingOver` modifier will check the incorrect item for users with existing rollovers:\\n```\\nQueueItem memory item = rolloverQueue[getRolloverIndex(_receiver)];\\nif (\\n    item.epochId == _epochId &&\\n    (balanceOf(_receiver, _epochId) - item.assets) < _assets\\n) revert AlreadyRollingOver();\\n```\\n\\nallowing the user to withdraw assets that should've been locked.

Impact:
Users are able to withdraw assets that should've been locked for rollovers.

Recommended mitigation:
The `ownerToRollOverQueueIndex` should be pointing to the last item in the queue in the `else` case only: when the user does not have an existing rollover queue item.\\n```\\n} else {\\n  // if not, add to queue\\n  rolloverQueue.push(\\n      QueueItem({\\n          assets: _assets,\\n          receiver: _receiver,\\n          epochId: _epochId\\n      })\\n  );\\n// Add the line below\\n ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length;\\n}\\n// Remove the line below\\n ownerToRollOverQueueIndex[_receiver] = rolloverQueue.length;\\n```\\n
Vulnerability: When rolling over, user will lose his winnings from previous epoch
Severity: high

Description:
When `mintRollovers` is called, when the function mints shares for the new epoch for the user, the amount of shares minted will be the same as the original assets he requested to rollover - not including the amount he won. After this, all these asset shares from the previous epoch are burnt. So the user won't be able to claim his winnings.\\nWhen user requests to `enlistInRollover`, he supplies the amount of assets to rollover, and this is saved in the queue.\\n```\\nrolloverQueue[index].assets = _assets;\\n```\\n\\nWhen `mintRollovers` is called, the function checks if the user won the previous epoch, and proceeds to burn all the shares the user requested to roll:\\n```\\n            if (epochResolved[queue[index].epochId]) {\\n                uint256 entitledShares = previewWithdraw(\\n                    queue[index].epochId,\\n                    queue[index].assets\\n                );\\n                // mint only if user won epoch he is rolling over\\n                if (entitledShares > queue[index].assets) {\\n                    // rest of code\\n                    // @note we know shares were locked up to this point\\n                    _burn(\\n                        queue[index].receiver,\\n                        queue[index].epochId,\\n                        queue[index].assets\\n                    );\\n```\\n\\nThen, and this is the problem, the function mints to the user his original assets - `assetsToMint` - and not `entitledShares`.\\n```\\nuint256 assetsToMint = queue[index].assets - relayerFee;\\n_mintShares(queue[index].receiver, _epochId, assetsToMint);\\n```\\n\\nSo the user has only rolled his original assets, but since all his share of them is burned, he will not be able anymore to claim his winnings from them.\\nNote that if the user had called `withdraw` instead of rolling over, all his shares would be burned, but he would receive his `entitledShares`, and not just his original assets. We can see in this in `withdraw`. Note that `_assets` is burned (like in minting rollover) but `entitledShares` is sent (unlike minting rollover, which only remints _assets.)\\n```\\n        _burn(_owner, _id, _assets);\\n        _burnEmissions(_owner, _id, _assets);\\n        uint256 entitledShares;\\n        uint256 entitledEmissions = previewEmissionsWithdraw(_id, _assets);\\n        if (epochNull[_id] == false) {\\n            entitledShares = previewWithdraw(_id, _assets);\\n        } else {\\n            entitledShares = _assets;\\n        }\\n        if (entitledShares > 0) {\\n            SemiFungibleVault.asset.safeTransfer(_receiver, entitledShares);\\n        }\\n        if (entitledEmissions > 0) {\\n            emissionsToken.safeTransfer(_receiver, entitledEmissions);\\n        }\\n```\\n

Impact:
User will lose his rewards when rolling over.

Recommended mitigation:
Either remint the user his winnings also, or if you don't want to make him roll over the winnings, change the calculation so he can still withdraw his shares of the winnings.
Vulnerability: Adversary can break deposit queue and cause loss of funds
Severity: high

Description:
Carousel.sol#L531-L538\\n```\\nfunction _mintShares(\\n    address to,\\n    uint256 id,\\n    uint256 amount\\n) internal {\\n    _mint(to, id, amount, EMPTY);\\n    _mintEmissions(to, id, amount);\\n}\\n```\\n\\nWhen processing deposits for the deposit queue, it _mintShares to the specified receiver which makes a _mint subcall.\\nERC1155.sol#L263-L278\\n```\\nfunction _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {\\n    require(to != address(0), "ERC1155: mint to the zero address");\\n\\n    address operator = _msgSender();\\n    uint256[] memory ids = _asSingletonArray(id);\\n    uint256[] memory amounts = _asSingletonArray(amount);\\n\\n    _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);\\n\\n    _balances[id][to] += amount;\\n    emit TransferSingle(operator, address(0), to, id, amount);\\n\\n    _afterTokenTransfer(operator, address(0), to, ids, amounts, data);\\n\\n    _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);\\n}\\n```\\n\\nThe base ERC1155 _mint is used which always behaves the same way that ERC721 safeMint does, that is, it always calls _doSafeTrasnferAcceptanceCheck which makes a call to the receiver. A malicious user can make the receiver always revert. This breaks the deposit queue completely. Since deposits can't be canceled this WILL result in loss of funds to all users whose deposits are blocked. To make matters worse it uses first in last out so the attacker can trap all deposits before them

Impact:
Users who deposited before the adversary will lose their entire deposit

Recommended mitigation:
Override _mint to remove the safeMint behavior so that users can't DOS the deposit queue
Vulnerability: Controller doesn't send treasury funds to the vault's treasury address
Severity: medium

Description:
The Controller contract sends `treasury` funds to its own immutable `treasury` address instead of sending the funds to the one stored in the respective vault contract.\\nEach vault has a treasury address that is assigned on deployment which can also be updated through the factory contract:\\n```\\n    constructor(\\n        // // rest of code\\n        address _treasury\\n    ) SemiFungibleVault(IERC20(_assetAddress), _name, _symbol, _tokenURI) {\\n        // // rest of code\\n        treasury = _treasury;\\n        whitelistedAddresses[_treasury] = true;\\n    }\\n\\n    function setTreasury(address _treasury) public onlyFactory {\\n        if (_treasury == address(0)) revert AddressZero();\\n        treasury = _treasury;\\n    }\\n```\\n\\nBut, the Controller, responsible for sending the fees to the treasury, uses the immutable treasury address that it was initialized with:\\n```\\n    constructor(\\n        // // rest of code\\n        address _treasury\\n    ) {\\n        // // rest of code\\n        treasury = _treasury;\\n    }\\n\\n    // @audit just one example. Search for `treasury` in the Controller contract to find the others\\n    function triggerEndEpoch(uint256 _marketId, uint256 _epochId) public {\\n        // // rest of code\\n        \\n        // send premium fees to treasury and remaining TVL to collateral vault\\n        premiumVault.sendTokens(_epochId, premiumFee, treasury);\\n        // strike price reached so collateral is entitled to collateralTVLAfterFee\\n        premiumVault.sendTokens(\\n            _epochId,\\n            premiumTVLAfterFee,\\n            address(collateralVault)\\n        );\\n\\n        // // rest of code\\n    }\\n```\\n

Impact:
It's not possible to have different treasury addresses for different vaults. It's also not possible to update the treasury address of a vault although it has a function to do that. Funds will always be sent to the address the Controller was initialized with.

Recommended mitigation:
The Controller should query the Vault to get the correct treasury address, e.g.:\\n```\\ncollateralVault.sendTokens(_epochId, collateralFee, collateralVault.treasury());\\n```\\n
Vulnerability: User deposit may never be entertained from deposit queue
Severity: medium

Description:
Due to FILO (first in last out) stack structure, while dequeuing, the first few entries may never be retrieved. These means User deposit may never be entertained from deposit queue if there are too many deposits\\nAssume User A made a deposit which becomes 1st entry in `depositQueue`\\nPost this X more deposits were made, so `depositQueue.length=X+1`\\nRelayer calls `mintDepositInQueue` and process `X-9` deposits\\n```\\n while ((length - _operations) <= i) {\\n            // this loop impelements FILO (first in last out) stack to reduce gas cost and improve code readability\\n            // changing it to FIFO (first in first out) would require more code changes and would be more expensive\\n            _mintShares(\\n                queue[i].receiver,\\n                _epochId,\\n                queue[i].assets - relayerFee\\n            );\\n            emit Deposit(\\n                msg.sender,\\n                queue[i].receiver,\\n                _epochId,\\n                queue[i].assets - relayerFee\\n            );\\n            depositQueue.pop();\\n            if (i == 0) break;\\n            unchecked {\\n                i--;\\n            }\\n        }\\n```\\n\\nThis reduces deposit queue to only 10\\nBefore relayer could process these, Y more deposits were made which increases deposit queue to `y+10`\\nThis means Relayer might not be able to again process User A deposit as this deposit is lying after processing `Y+9` deposits

Impact:
User deposit may remain stuck in deposit queue if a large number of deposit are present in queue and relayer is interested in dequeuing all entries

Recommended mitigation:
Allow User to dequeue deposit queue based on index, so that if such condition arises, user would be able to dequeue his deposit (independent of relayer)