xanoutas/solidity-security-findings huggingface dataset:
Upgraded Q -> 2 from #74 [1717590525923]
Audit: 2024-05-munchables-findings
Severity: MEDIUM

Judge has assessed an item in Issue #74 as 2 risk. The relevant finding follows:

 ## **[L-02] Inconsistent conditions for approving and disapproving the proposal price**

The approval and disapproval proposer price mechanism does not allow the `PriceFeed_X` that was approved first to become disapproved. 

However, this logic does not implement the vice versa scenario, as the mechanism allows `PriceFeed_X` that was disapproved first to become approved afterward. 

This results in an inconsistent mechanism and incorrectly tracks `usdUpdateProposal.approvalsCount` and `usdUpdateProposal.disapprovalsCount`.

Location: [LockManager::approveUSDPrice()](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177-L207)
```solidity
File: ..
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
        usdUpdateProposal.approvalsCount++;

        if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) {
            _execUSDPriceUpdate();
        }

        emit ApprovedUSDPrice(msg.sender);
    }
```
Use of .transfer() Instead of safeTransfer
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L423


# Vulnerability details

**Description:** 
The LockManager contract uses the .transfer() method to send ERC20 tokens, specifically in the _lock and unlock functions. The .transfer() method is known to have limitations when dealing with tokens that do not strictly adhere to the ERC20 standard or when dealing with contracts that implement custom logic in their transfer or fallback functions.

**Impact:** 
Some ERC20 tokens implement custom logic that could cause transfers to fail if not handled properly, potentially leading to locked funds.

**Proof of Concept:**
The vulnerability is present in the unlock function:
```javascript
function unlock(address _tokenContract, uint256 _quantity) external notPaused nonReentrant {
    LockedToken storage lockedToken = lockedTokens[msg.sender][_tokenContract];
    if (lockedToken.quantity < _quantity)
        revert InsufficientLockAmountError();
    if (lockedToken.unlockTime > uint32(block.timestamp))
        revert TokenStillLockedError();

    accountManager.forceHarvest(msg.sender);

    lockedToken.quantity -= _quantity;

    // send token
    if (_tokenContract == address(0)) {
        payable(msg.sender).transfer(_quantity); // ETH transfer
    } else {
        IERC20 token = IERC20(_tokenContract);
@>      token.transfer(msg.sender, _quantity); // Token transfer
    }

    emit Unlocked(msg.sender, _tokenContract, _quantity);
}

```

**Recommended Mitigation:** 
To ensure compatibility and safety, replace .transfer() with OpenZeppelin’s safeTransfer method for ERC20 tokens. 


## Assessed type

ERC20
Silent Token Transfer Failure in LockManager Contract Leads to Incorrect Accounting and Potential Loss of Funds
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L419-L424


# Vulnerability details

## Impact
The `LockManager` contract maintains an internal accounting of the locked token balances for each user. If a token transfer fails silently during the unlock operation, the contract will update its internal accounting as if the transfer was successful, even though the tokens were not actually transferred back to the user's account. **_This will result in incorrect accounting of token balances within the contract, leading to discrepancies between the contract's internal state and the actual token balances._**

The issue lies in the following lines: [/LockManager.sol#L419-L423](https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L419-L424)
```solidity
if (_tokenContract == address(0)) {
    payable(msg.sender).transfer(_quantity);
} else {
    IERC20 token = IERC20(_tokenContract);
    token.transfer(msg.sender, _quantity);
}
```
The problem is that the `transfer` function of the `IERC20` interface does not have a return value, which means that the success or failure of the token transfer is not checked.

If the token contract being used does not follow the ERC20 standard correctly and does not revert on failed transfers, the token transfer could fail silently, leading to potential loss of funds or incorrect accounting of token balances.

## Proof of Concept
In the Ethereum ecosystem, the ERC20 token standard is widely adopted, and it defines a set of rules and functions that token contracts should implement. One of these functions is the transfer function, which is used to transfer tokens from the caller's account to another account.

According to the ERC20 standard, the `transfer` function should return a boolean value indicating whether the transfer was successful or not. However, some token contracts do not follow this standard correctly and do not return a value or revert the transaction in case of a failed transfer.

Example of a non-compliant ERC20 token contract that does not return a value from the transfer function:
```solidity
pragma solidity ^0.8.0;

contract NonCompliantERC20 {
    mapping(address => uint256) public balances;

    function transfer(address _to, uint256 _value) public {
        // Perform some checks and update balances
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        // No return value
    }
}
```
In the `unlock` function of the `LockManager` contract, the code assumes that the transfer function of the ERC20 token contract will either succeed or revert the transaction. However, with non-compliant token contracts like the one above, the transfer can fail silently without reverting the transaction.

Scenario that illustrates the potential impact:

Alice locks 100 tokens of a non-compliant ERC20 token in the `LockManager` contract.
The
Missing return value check for transferFrom in _lock function
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L373-L377


# Vulnerability details

## Impact
The _lock function doesn't check the return value of `transferFrom` function when transferring ERC-20 tokens. Right after calling `transferFrom`, function updates the token quantity assuming that `transferFrom` returned true. However, for certain ERC20 tokens, if insufficient tokens are present, no revert occurs but a result of `false` is returned. So its important to check this. If you don't, you could mint NFTs without have received sufficient tokens to do so. So you could loose funds.

Please check the same finding from PoolTogether, Visor contests.
https://code4rena.com/reports/2024-03-pooltogether#m-06-funds-locked-due-to-missing-transfer-check
https://code4rena.com/reports/2021-05-visorfinance#m-01-unhandled-return-value-of-transferfrom-in-timelockerc20-could-lead-to-fund-loss-for-recipients


## Proof of Concept

The issue is in the _lock function, where the transferFrom is called without checking its return value. Here is the relevant part of the code:
```
// Transfer erc tokens
        if (_tokenContract != address(0)) {
            IERC20 token = IERC20(_tokenContract);
            token.transferFrom(_tokenOwner, address(this), _quantity);
        }
```
If the transferFrom function returns false, the contract will still proceed as if the transfer was successful. 

```if (_tokenContract != address(0)) {
            IERC20 token = IERC20(_tokenContract);
            token.transferFrom(_tokenOwner, address(this), _quantity);
        }

        lockedToken.remainder = remainder;
        lockedToken.quantity += _quantity;
```

Above you can see the next part of the function, added the _quantity to lockedToken.quantity without checking if the transfer was successful.


## Tools Used
Manual Review

## Recommended Mitigation Steps
use safeERC20, or add the following line as best practice.
```if (!success) revert TokenTransferFailedError();```












## Assessed type

ERC20
Funds locked due to missing transfer check
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L423


# Vulnerability details

## Impact
The [LockManager::unlock](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L401-L427) function ERC20 tokens `transfer` without checking its return value. According to the ERC20 standard, the transfer function "returns a boolean value indicating whether the operation succeeded" ([OZ's Docs](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#:~:text=transfer(address%20recipient,%20uint256%20amount))). If the transfer fails and returns false, the function will proceed as if the transfer was successful, leading to a discrepancy between the contract's recorded state and the actual token balances. This can result in users not receiving their tokens while the contract incorrectly assumes the transfer succeeded, effectively causing a loss of access to those tokens for the user.

## Proof of Concept
1. Let's assume that Alice had 1000 tokens locked in. 
2. She proceeds to unlock these 1000 tokens.
3. [`token.transfer(msg.sender, 1000)`](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L423) silently fails and returns `false`.
4. However, the contract had already decreased [`lockedToken.quantity -= _quantity`](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L416) by 1000 tokens and proceeds as if the transfer succeeded.
5. Alice does not receive her 1000 ERC20 tokens, but the contract's state reflects that the tokens were transferred successfully.

At this point, Alice's assets are locked in the `LockManager` contract. They cannot be unlocked at a later point, because the corresponding lockedToken quantity is already deducted.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement OpenZeppelin's [SafeERC20 `safeTransfer`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8cc7f2dcbf368f2a7ea491389dae41f01c16e352/contracts/token/ERC20/utils/SafeERC20.sol#L32-L38) function.

```diff
function unlock(
    address _tokenContract,
    uint256 _quantity
) external notPaused nonReentrant {
...
    // send token
    if (_tokenContract == address(0)) {
        payable(msg.sender).transfer(_quantity);
    } else {
        IERC20 token = IERC20(_tokenContract);
-        token.transfer(msg.sender, _quantity);
+       token.safeTransfer(msg.sender, _quantity);
    }

    emit Unlocked(msg.sender, _tokenContract, _quantity);
}

```


## Assessed type

Token-Transfer
Unchecked Token Transfer
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L422-L423


# Vulnerability details

## Impact

The function `unlock` transfers ERC20 tokens without checking the return value from `transfer` function.

If the LockManager is ever used with a token contract that does return false on error instead of reverting, the `unlock` function would decrease the locked token quantity without actually having transferred the tokens in case of an error during `transfer`.

## Proof of Concept

While the latest ERC20 tokens should revert on failure, not checking the return value could potentially allow silent failures in transfers as the original ERC20 spec allowed returning false in case of an error.

See [LockManager.sol:422-423](https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L422-L423).

## Tools Used

Unreleased AI audit tool.

## Recommended Mitigation Steps

Check the `transfer` function's return value or use OpenZeppelin's [SafeERC20](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#SafeERC20).


## Assessed type

ERC20
Missing `transferFrom` check leading to locking exploit
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L374-L377


# Vulnerability details

## Impact
In the [LockManager::_lock](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L311-L371) function, the `transferFrom()` is used to actually transfer the tokens from the owner to the contract. According to the ERC20 standard, the transfer function "returns a boolean value indicating whether the operation succeeded" ([OZ's Docs](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#IERC20-transferFrom-address-address-uint256-)). If the transfer fails and returns false, the function will proceed as if the transfer was successful. The impact is that for an arbitrary ERC20 token, this `transferFrom()` call may return `false` but the `_lock()` logic will miss that, and assume it was successfully transferred into the contract and updates the `lockedToken.quantity += _quantity` accounting accordingly. This will result in the user locking tokens for free.

## Proof of Concept
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L374-L377

1. Alice wants to lock 10000 ERC20 tokens and calls the `lock()` function.
2. The `transferFrom` inside `_lock` fails due to exceeding the gas limit and returns `false`.
3. However, the return value is not checked, and the execution succeeds, adding the quantity into the lockedToken `lockedToken.quantity += _quantity`.
4. Alice successfully locked 10000 token for free.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Check the success boolean of the `transferFrom()` call. Alternatively, use OZ’s `SafeERC20’s safeTransferFrom()` function.


## Assessed type

Token-Transfer
Protocol doesn't work with fee on transfer tokens
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L311-L398
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L401-L427


# Vulnerability details

## Impact
Protocol does not account for fee on transfer. This would lead to loss for the protocol.

## Proof of Concept
According to the README.md, we are to assume that the protocol could add more tokens in the future for use in LockManager. How ever the current implementation will not work with fee on transfer tokens.

Upon locking tokens the user's `lockedToken.quantity` gets increased by `_quantity`, when it should be getting increased by `_quantity - fee` in the case of fee on transfer tokens.

This will lead to loss for the protocol, since in the `unlock` function, the `token.transfer(msg.sender, _quantity);` line will send the quantity back to the user, with the protocol having to pay the fees for the fee on transfer.
```Solidity
function _lock(
        address _tokenContract,
        uint256 _quantity,
        address _tokenOwner,
        address _lockRecipient
    ) private {
        (
            address _mainAccount,
            MunchablesCommonLib.Player memory _player
        ) = accountManager.getPlayer(_lockRecipient);
        if (_mainAccount != _lockRecipient) revert SubAccountCannotLockError();
        if (_player.registrationDate == 0) revert AccountNotRegisteredError();
        // check approvals and value of tx matches
        if (_tokenContract == address(0)) {
            if (msg.value != _quantity) revert ETHValueIncorrectError();
        } else {
            if (msg.value != 0) revert InvalidMessageValueError();
            IERC20 token = IERC20(_tokenContract);
            uint256 allowance = token.allowance(_tokenOwner, address(this));
            if (allowance < _quantity) revert InsufficientAllowanceError();
        }

        LockedToken storage lockedToken = lockedTokens[_lockRecipient][
            _tokenContract
        ];
        ConfiguredToken storage configuredToken = configuredTokens[
            _tokenContract
        ];

        // they will receive schnibbles at the new rate since last harvest if not for force harvest
        accountManager.forceHarvest(_lockRecipient);

        // add remainder from any previous lock
        uint256 quantity = _quantity + lockedToken.remainder;
        uint256 remainder;
        uint256 numberNFTs;
        uint32 _lockDuration = playerSettings[_lockRecipient].lockDuration;

        if (_lockDuration == 0) {
            _lockDuration = lockdrop.minLockDuration;
        }
        if (
            lockdrop.start <= uint32(block.timestamp) &&
            lockdrop.end >= uint32(block.timestamp)
        ) {
            if (
                _lockDuration < lockdrop.minLockDuration ||
                _lockDuration >
                uint32(configStorage.getUint(StorageKey.Ma
Fee-On-Transfer Tokens Are Not Supported
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L374-L380


# Vulnerability details


## Impact

The `_lock` function in the `LockManager` contract uses the `transferFrom` method to transfer tokens. The contract claims to support fee-on-transfer tokens, which deduct a fee from the transferred amount. However, the function does not account for the actual amount received, which leads to discrepancies between the intended and actual locked quantities. This can result in incorrect token balances, miscalculations, and potential financial loss for the protocol.

## Proof of Concept

In the `LockManager` contract, the `_lock` function includes the following code:

```solidity
if (_tokenContract != address(0)) {
    IERC20 token = IERC20(_tokenContract);
    token.transferFrom(_tokenOwner, address(this), _quantity);
}

lockedToken.remainder = remainder;
lockedToken.quantity += _quantity;
```

When a fee-on-transfer token is used, the `transferFrom` call deducts a fee, meaning the actual amount received by the contract is less than `_quantity`. For instance, if the user specifies 100 tokens and a 2% fee is applied, only 98 tokens are received. The function, however, assumes 100 tokens are received, leading to incorrect balances and potential financial discrepancies.

## Tools Used

- Manual code review

## Recommended Mitigation Steps

To accurately reflect the received token amount, calculate the difference in token balance before and after the `transferFrom` call and update the `lockedToken.quantity` accordingly. Here’s how you can refactor the `_lock` function:

1. Import the SafeERC20 library:

   ```solidity
   import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
   ```

2. Use `safeTransferFrom` and calculate the received amount:

   ```solidity
   if (_tokenContract != address(0)) {
       IERC20 token = IERC20(_tokenContract);
       uint256 balanceBefore = token.balanceOf(address(this));
       SafeERC20.safeTransferFrom(token, _tokenOwner, address(this), _quantity);
       uint256 balanceAfter = token.balanceOf(address(this));
       uint256 receivedAmount = balanceAfter - balanceBefore;

       lockedToken.quantity += receivedAmount;
   }
   ```

This approach ensures that the actual amount received is accurately reflected in the `lockedToken.quantity`, thereby preventing miscalculations and financial discrepancies.

By implementing these changes, the contract will handle fee-on-transfer tokens correctly, maintaining accurate token balances and preventing potential financial losses for the protocol.



## Assessed type

Token-Transfer
Using Fee-On-Transfer Tokens will lead to user token loss.
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L374-L380
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L421-L424


# Vulnerability details

According to readme `ERC20 token behaviors in scope` table this behavior is in scope.

## Impact
After `lock()` fee-on-transfer token the amount of tokens passed as a parameter will be stored in the contract, but the actual amount of tokens received by the contract will be less.
```solidity
        lockedToken.quantity += _quantity;
```
If this token is used frequently the actual balance of this token in the contract and sum of all users balances will be different.
This lead to a situation when last user that will `unlock()` his tokens won't be able to do this. 
Because the contract balance will be less than he locked.
As a result, user will lose his tokens.


## Proof of Concept
Assume that fee-on-transfer token has 10% fee on transfer.

1. Alice locks 100 tokens in the contract. (90 tokens contract receives)
2. Bob locks 100 tokens in the contract. (90 tokens contract receives)
3. Alice unlocks 100 tokens. (contact sends 100 tokens to Alice - she receives 90 tokens)
  - contract balance: 80 tokens
4. Bob tries to unlock his 100 tokens, but since contract balance is 80 tokens tx will revert.

## Tools Used
manual review

## Recommended Mitigation Steps
To avoid this behavior, `lockedToken.quantity` should be updated with the actual amount of tokens received by the contract.

```diff
        if (_tokenContract != address(0)) {
            IERC20 token = IERC20(_tokenContract);
+           uint256 balanceBefore = token.balanceOf(address(this));
            token.transferFrom(_tokenOwner, address(this), _quantity);
+           uint256 balanceAfter = token.balanceOf(address(this));
+           lockedToken.quantity += balanceAfter - balanceBefore;
+       } else {
+           lockedToken.quantity += _quantity;
+       }

        lockedToken.remainder = remainder;
-       lockedToken.quantity += _quantity;
```


## Assessed type

Token-Transfer