xanoutas/solidity-security-findings huggingface dataset:
Contract doesn't handle ERC20 token which `don't revert on failure` which cause user fund loss
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
This has been reported by [4naly3er](https://github.com/code-423n4/2024-05-munchables/blob/main/4naly3er-report.md#m-3-return-values-of-transfertransferfrom-not-checked) as `M-3` and `M-4`, but the impact would be more severe than Medium, which is why I still submit this report providing richer details about the issue.

Furthermore, considering ERC20 which `doesn't on revert on failure` is in scope according to the README contest.
```
ERC20 token behaviors in scope
Doesn't revert on failure	Yes
```

Whenever a player wants to lock their token they will call `LockManager::lock`. The current implementation present a `major flaw` for players who wants to lock `ERC20 token`, as the contract is using `transferFrom` to transfer those tokens and is `not checking the returned value` to detect if the transfer was a failure or not, and in such case for token that doesn't revert on transfer failure, the flow will just continue which will cause of lot of damage in the current application, which seems to warrant `High` severity.

## Impacts
1. Player can steal others players ERC20 from the contract. (lock using transferFrom)
2. Player who unlock might not get all their token back.    (unlock using transfer)

## PoC
Here is a example of `Impact #1`.  how Bob will steal Alice's [ZRXToken](https://etherscan.io/address/0xe41d2489571d322189246dafa5ebde1f4699f498#code) token (which doesn't revert on transfer failure).

1. Alice call `lock` for an amount of `100 ZRX token`.
2. Bob decide to steal Alice's funds (but that could be all the ZRX token holding by the contract)
 - He first call `setLockDuration` with 1 second (as reported by my other issue, since minimum duration `can be bypass`, that will work)
 - Then he call `lock` with `100 ZRX` but with doesn't own any in reality, but as you can see, the function will simply return false `without reverting`. 
 ```solidity
     function transferFrom(address _from, address _to, uint _value)
        public
        returns (bool)
    {
        uint allowance = allowed[_from][msg.sender];
        if (balances[_from] >= _value
            && allowance >= _value
            && balances[_to] + _value >= balances[_to]
        ) {
            balances[_to] += _value;
            balances[_from] -= _value;
            if (allowance < MAX_UINT) {
                allowed[_from][msg.sender] -= _value;
            }
            Transfer(_from, _to, _value);
            return true;
        } else {
            return false;
        }
    }
 ```
 - So the contract will account that Bob now has also `100 ZRX token locked`, same as for Alice (but it's not true, the contract still only hold Alice's token)
3. Since Bob have a duration of only one second, he can submit another transaction almost ri
Locked tokens may be irretrievable if no-revert-on-failure tokens are used 
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Impact is HIGH. This issue could potentially lead to loss of funds and can make a user unable to unlock their locked tokens.

## Proof of Concept

For the scope of this audit, the [README](https://github.com/code-423n4/2024-05-munchables#general-questions) states that we can assume more tokens can be added for use in `LockManager` in future.

|Question|	Answer|
|-|-|
|ERC20 used by the protocol	|USDB, WETH, (assume we can add more to the future for use in LockManager)|

[Also according to the README,](https://github.com/code-423n4/2024-05-munchables#erc20-token-behaviors-in-scope) 

ERC20 token behaviors in scope:
| Question| Answer|
|-|-|
|Doesn't revert on failure|	Yes|

In the `LockManager.sol` contract, the `transferFrom` and transfer function is used without checking its return value:

```solidity
IERC20 token = IERC20(_tokenContract);
token.transferFrom(_tokenOwner, address(this), _quantity);
```

```solidity
 // send token
        if (_tokenContract == address(0)) {
            payable(msg.sender).transfer(_quantity);
        } else {
            IERC20 token = IERC20(_tokenContract);
            token.transfer(msg.sender, _quantity);
        }
```


Now, `ZRX` is a token that fits these conditions and can theoretically be used in `LockManager` as a principal token. https://coinmarketcap.com/dexscan/ethereum/0xfa97aa5002124fe7edd36584628300e8d81df042/

ZRX is a token that does not revert on failure, instead it simply returns a bool value that is not checked.

If `_tokenContract` is a token that does not revert on failure, this call could fail silently, allowing the rest of the function to execute as if the transfer were successful.

Similarly, `BNB` just like ZRX does not revert on failure, instead it simply returns a bool value that is not checked too: https://etherscan.io/token/0xB8c77482e45F1F44dE1745F52C74426C631bDD52



Now in the current scope, Unsafe transfers are used in [_lock](https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L376) and [unlock](https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L420-L423) functions.

Since these attempts to transfer could silently fail, this then leads to multiple scenarios like:

- Loss of Funds: If a user attempts to lock tokens using a token that does not revert on failure, and the transferFrom operation fails silently, the user might still believe their tokens are successfully locked. Albeit, due to the lack of actual token transfer, the user's tokens would not be properly recorded in the contract's state. This could lead to the user lo
Return value of token.transferFrom() not checked
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
The return value of the message call ```token.transferFrom()``` is not checked. This might lead to a serious problem wherein LockManager.sol could not have transferred the tokens to itself but updates the lockedToken data for the user.

## Proof of Concept
The following is a detailed step-by-step explanation.

A user X locks a token through the ```lock()``` function:

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

```
    function lock(
        address _tokenContract,
        uint256 _quantity
    )
        external
        payable
        notPaused
        onlyActiveToken(_tokenContract)
        onlyConfiguredToken(_tokenContract)
        nonReentrant
    {
        _lock(_tokenContract, _quantity, msg.sender, msg.sender);
    }
```

The LockManager.sol in the ```_lock()``` function transfers the tokens from the user to itself at LOC-376:

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

```
        if (_tokenContract != address(0)) {
            IERC20 token = IERC20(_tokenContract);
            token.transferFrom(_tokenOwner, address(this), _quantity);
        }
```
Let us assume this token (that X locks) does not revert on failure but returns a bool (this token behavior is in scope).

The ```_lock()``` function does not check the returned bool. Let us assume that the token transfer (from X to LockManager.sol) failed. However, ```_lock()``` function would not be aware of it and set the ```lockedTokens[X][_tokenContract]``` data for X as usual (that is, as it would set in the normal conditions when the token transfer would not have failed).

This allows X to enjoy the benefits of token lock without any locking of token in reality.

Moreover, if X calls ```unlock()``` function with a positive ```_quantity```, the LockManager.sol would lose tokens to X:

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

```
    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();

        // force harvest to make sure that they get the schnibbles that they are entitled to
        accountManager.forceHarvest(msg.sender);

        lockedToken.quantity -= _quantity;

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

        em
Use safeTransfer consistently instead of transfer
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If the token send fails, the player may loss locked tokens

## Proof of Concept
OpenZeppelin’s IERC20 interface are expected to return a bool value after a successful transfer:

openzeppelin/contracts/token/ERC20/IERC20.sol
```solidity
    function transfer ( address to , uint256 amount ) external returns ( bool );
```
Also, in the `ReadMe` file of `Code4Rena Audit`, it is mentioned that more ERC20 tokens to be used in `LockManager` will be added in the future.
> Scoping Q & A:
> ERC20 used by the protocol:	USDB, WETH, (assume we can add more to the future for use in LockManager)

Based on the above facts, certain assets such as ZRX can be added to `LockManager`.
These specific tokens are not reverted and return false if token transmission fails.
However, the `LockManager.sol#unlock()` function does not check the return value if token send fails.
```solidity
    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();

        // force harvest to make sure that they get the schnibbles that they are entitled to
        accountManager.forceHarvest(msg.sender);

416:    lockedToken.quantity -= _quantity;

        // send token
        if (_tokenContract == address(0)) {
            payable(msg.sender).transfer(_quantity);
        } else {
            IERC20 token = IERC20(_tokenContract);
423:        token.transfer(msg.sender, _quantity);
        }

        emit Unlocked(msg.sender, _tokenContract, _quantity);
    }
```
Some ERC20 tokens are not reverted when token send fails and return a bool value, so the `unlock()` function is not reverted.
Therefore, if token send fails, Locked assets corresponding to `_quantity` cannot be unlocked, and the assets may be locked in the contract forever by #L423.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider using `safeTransfer` consistently instead of `transfer`.
```solidity
    function unlock(
        address _tokenContract,
        uint256 _quantity
    ) external notPaused nonReentrant {
        ... ...
        else {
            IERC20 token = IERC20(_tokenContract);
---         token.transfer(msg.sender, _quantity);
+++         token.safeTransfer(msg.sender, _quantity);            
        }
        ... ...
    }
```


## Assessed type

Token-Transfer
Users can increase their locked balance without actually transferring tokens when no-revert-on-failure tokens are used 
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
An attacker can call the `_lock()` function with a very large `_quantity()` of tokens as argument that would cause `transferFrom()` to return false but not revert. `_lock()` function would continue execution and update the attacker's locked balance accordingly. This way attacker can increase their locked balance without actually transferring tokens due to the issue of tokens that do-not-revert-on-failure.

## Proof of Concept
According to the protocol'e readme, tokens that does not revert on failure are in scope for this audit contest:

> Doesn't revert on failure-	Yes

For instance, `ZRX` is a token that do not revert on failure, instead it simply returns a boolean value that is not checked:

```js
function transferFrom(address _from, address _to, uint _value) returns (bool) {
        if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value >= balances[_to]) {
            balances[_to] += _value;
            balances[_from] -= _value;
            allowed[_from][msg.sender] -= _value;
            Transfer(_from, _to, _value);
            return true;
        } else { return false; }
    }
```
An attacker can take advantage of this, because the `_lock()` function uses an outdated token transfer method:
[LockManager.sol#L373-L377](https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L373-L377)
```js
        // Transfer erc tokens
        if (_tokenContract != address(0)) {
            IERC20 token = IERC20(_tokenContract);
            token.transferFrom(_tokenOwner, address(this), _quantity);
        }
```
 _Example Scenario:_

1. Alice attempts to lock 10000 ZRX tokens using the `_lock()` function.
2. However, the token transfer fails due to an issue with the token contract (e.g., insufficient funds).
3. As the transferFrom() method of ZRX token does not revert on failure but returns false, the _lock() function does not detect the transfer failure and continues execution.
4. Despite the transfer failure, the `_lock()` function updates Alice's locked balance in the protocol by 10000 tokens.

This way, Alice was able to increase his locked balance in the contract without actually transferring any tokens.
[LockManager.sol#L379-L384](https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L379-L384)
```js
        lockedToken.remainder = remainder;
        lockedToken.quantity += _quantity;
        lockedToken.lastLockTime = uint32(block.timestamp);
        lockedToken.unlockTime =
            uint32(block.timestamp) +
            uint32(_lockDuration);
```

##
Incorrect Handling of ERC20 Transfers in Locking Mechanism
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `_lock` function in the smart contract does not correctly handle the result of the `transferFrom` call when transferring ERC20 tokens. This oversight allows an attacker to lock tokens without actually transferring them to the contract, which can lead to unauthorized access and withdrawal of tokens from the protocol.
## Proof of Concept
```javascript  
function _lock(
    address _tokenContract,
    uint256 _quantity,
    address _tokenOwner,
    address _lockRecipient
) private {
    // ... (other code)

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

    // ... (other code)
}
```
The function `token.transferFrom(_tokenOwner, address(this), _quantity)` is called without checking its return value. According to the ERC20 standard, `transferFrom` should return a boolean indicating the success of the transfer. If the transfer fails the function returns false, but the current implementation does not handle this case, and the _lock function proceeds to update the contract state as if the transfer succeeded.

Imagine this scenario:
  1. An attacker can approve the contract to spend tokens from an account that 
     does not hold sufficient tokens.
  2. Call the `lock` function to lock the tokens.
  3. The `transferFrom` call fails, but _lock function continues and incorrectly 
     records the tokens as locked.
  4. The attacker can later call the unlock function to withdraw tokens that were 
     never actually transferred to the contract.

## Tools Used
manual review

## Recommended Mitigation Steps
Use the OpenZeppelin SafeERC20 library to ensure that all ERC20 token transfers are correctly handled. The SafeERC20 library provides wrappers around the ERC20 operations that handle cases where the token contract does not return a boolean or returns false on failure.


## Assessed type

ERC20
Unchecked ERC20 transfers can cause lock up
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

When a user calls `unlock()` ERC-20 tokens get transferred to him but the case where the token transfer does not succeed is not handled. His funds can get locked because of this.

## Impact
Loss of funds

## Proof of Concept
Some ERC-20 tokens do not revert on failiure but instead return false. This means that if the transfer fails nothing will happen but `lockedToken.quantity` will still get updated. This variable determines how many tokens a user can withdraw.
```solidity
        lockedToken.quantity -= _quantity;

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


## Tools Used
Manual Review

## Recommended Mitigation Steps
Use OpenZeppelin’s SafeERC20
```diff
        if (_tokenContract == address(0)) {
            payable(msg.sender).transfer(_quantity);
        } else {
            IERC20 token = IERC20(_tokenContract);
-           token.transfer(msg.sender, _quantity);
+           token.safeTransfer(msg.sender, _quantity);
        }
```


## Assessed type

ERC20
No checks for return value of token transfer
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

In the `unlock()` and `_lock()` functions in `LockManager.sol`, the user-specified token is transfered to/from the user. This `token.transfer`/`token.transferFrom` call's return value is not checked. In the Q&A section on the code4rena description page the following is stated:
`ERC20 used by the protocol:	USDB, WETH, (assume we can add more to the future for use in LockManager)`. Assuming any token might be added to the protocol, also tokens like `ZRX` might be added. Since the returnvalue of the `transfer`/`tranferFrom` calls is not checked as mentioned above, this will lead to problems if such tokens are used.

## Impact
Since some tokens like `ZRX` do not revert on failure when transfering them, users would be able to steal tokens from other users and users' tokens may be locked in the protocol.
Assuming the protocol supports `ZRX` in the future, if a user locks `ZRX` in the contract by calling the `lock` function, the user could not approve these tokens. In that case the `token.transferFrom` call will fail but not revert. Therefore the user's `lockedToken.quantity` will be increased but no tokens are transfered to the contract. If now another user transfers tokens into the protocol, the first malicious user can call `unlock` and since now tokens are in the protocol, they will receive those tokens without ever having depsosited any.

Other scenario: If a user tries to unlock their tokens and the `token.transfer` call fails, this will not be noticed. As the `lockedToken.quantity` is decreased, and the tokens are not transfered to the user but the functioncall ends successfully, these tokens will be locked in the protocol, causing the user to lose their tokens.

## Tools Used
Manual review

## Recommended Mitigation Steps
Add another if-statement like the following, ensuring that the transfer was successful:
(also add a check for the call in `_lock`)
```Solidity
[...]
} else {
    IERC20 token = IERC20(_tokenContract);
    if (!token.transfer(msg.sender, _quantity))
    {
      revert TransferFailed(...);
    }
}
[...]








## Assessed type

ERC20
Unlocking tokens can lead to user permanently losing his locked tokens.
Audit: 2024-05-munchables-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability Detail
unlock(); function does not have a check if the LockManager has enough token balance, thus since we are using .transfer() instead of safeTransfer and not checking the return status, the transfer will fail silently while the changes to `lockedToken.quantity` for the user will remain, thus the user will end up receiving those

One example which could lead to less tokens for the LockManager contract, which is related to:
On lock() if the token is a "Fee on Tranfer" token then funds will be lost as the _quantity set in the lockedToken.quantity also includes the fees, but the LockManager has not received them.


## Impact
This will lead to user funds being locked forever as unlock function does not check for the contract’s balance and the .transfer will fail silently.

## Recommendation
Always use SafeERC20 and safeTransfer, instead of .transfer();

Check account balance before and after transfers for Fee on Transfer tokens in the lock(); function.



## Assessed type

Token-Transfer